From 4b7692708ca7235ac20079db14cc39c0e9efaa42 Mon Sep 17 00:00:00 2001 From: Benny Malengier Date: Thu, 18 Jun 2009 21:56:37 +0000 Subject: [PATCH] Split CLI from GUI. These changes allow CLI to work without GTK Part 1. To do: pylint on new files. svn: r12674 --- autogen.sh | 2 +- po/POTFILES.in | 15 +- src/Assistant.py | 1 + src/DbState.py | 2 +- src/GrampsLogger/_ErrorReportAssistant.py | 20 +- src/GrampsLogger/_ErrorView.py | 7 +- src/GrampsLogger/_GtkHandler.py | 2 - src/Makefile.am | 7 +- src/cli/Makefile.am | 6 +- src/cli/__init__.py | 5 + src/{ArgHandler.py => cli/arghandler.py} | 450 ++++++++------------- src/cli/argparser.py | 256 ++++++++++++ src/cli/clidbman.py | 380 +++++++++++++++++ src/cli/grampscli.py | 284 +++++++++++++ src/glade/Makefile.am | 2 +- src/glade/{dbmanager.glade => dbman.glade} | 0 src/gramps.py | 142 +++---- src/gramps_main.py | 292 ------------- src/gui/Makefile.am | 6 +- src/gui/__init__.py | 5 + src/{DbLoader.py => gui/dbloader.py} | 78 ++-- src/{DbManager.py => gui/dbman.py} | 316 +-------------- src/{ViewManager.py => gui/viewmanager.py} | 215 ++++------ 23 files changed, 1304 insertions(+), 1189 deletions(-) rename src/{ArgHandler.py => cli/arghandler.py} (55%) create mode 100644 src/cli/argparser.py create mode 100644 src/cli/clidbman.py create mode 100644 src/cli/grampscli.py rename src/glade/{dbmanager.glade => dbman.glade} (100%) delete mode 100644 src/gramps_main.py rename src/{DbLoader.py => gui/dbloader.py} (85%) rename src/{DbManager.py => gui/dbman.py} (74%) rename src/{ViewManager.py => gui/viewmanager.py} (89%) diff --git a/autogen.sh b/autogen.sh index a10fbbaa1..1c39530a7 100755 --- a/autogen.sh +++ b/autogen.sh @@ -5,7 +5,7 @@ PKG_NAME="gramps" srcdir=`dirname $0` test -z "$srcdir" && srcdir=. -srcfile=$srcdir/src/gramps_main.py +srcfile=$srcdir/src/gramps.py REQUIRED_AUTOMAKE_VERSION=1.9 DIE=0 diff --git a/po/POTFILES.in b/po/POTFILES.in index aef0705d0..f7a061a2f 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -4,7 +4,6 @@ # Python files # src/ansel_utf8.py -src/ArgHandler.py src/Assistant.py src/AutoComp.py src/Bookmarks.py @@ -12,8 +11,6 @@ src/ColumnOrder.py src/const.py src/DateEdit.py src/Date.py -src/DbLoader.py -src/DbManager.py src/DdTargets.py src/DisplayState.py src/Errors.py @@ -22,7 +19,6 @@ src/ExportOptions.py src/GrampsAboutDialog.py src/GrampsCfg.py src/GrampsDisplay.py -src/gramps_main.py src/gramps.py src/ImgManip.py src/ListModel.py @@ -47,10 +43,13 @@ src/TransUtils.py src/TreeTips.py src/Utils.py src/UndoHistory.py -src/ViewManager.py # cli src/cli/__init__.py +src/cli/arghandler.py +src/cli/argparser.py +src/cli/clidbman.py +src/cli/grampscli.py # gen API src/gen/__init__.py @@ -182,6 +181,10 @@ src/gen/plug/docbackend/docbackend.py # gui - GUI code src/gui/__init__.py +src/gui/dbloader.py +src/gui/dbman.py +src/gui/grampsgui.py +src/gui/viewmanager.py # gui/views - the GUI views package src/gui/views/__init__.py @@ -823,7 +826,7 @@ src/glade/grampscfg.glade src/glade/dateedit.glade src/glade/editsource.glade src/glade/styleeditor.glade -src/glade/dbmanager.glade +src/glade/dbman.glade src/glade/editurl.glade src/glade/editrepository.glade src/glade/editreporef.glade diff --git a/src/Assistant.py b/src/Assistant.py index 0dc35ae83..1bfb59ced 100644 --- a/src/Assistant.py +++ b/src/Assistant.py @@ -170,6 +170,7 @@ class Assistant(gtk.Object, ManagedWindow.ManagedWindow): gtk.main_iteration() def destroy(self, *obj): + self.window.emit('delete-event', None) self.window.destroy() def do_get_property(self, prop): diff --git a/src/DbState.py b/src/DbState.py index be24371cb..afe909027 100644 --- a/src/DbState.py +++ b/src/DbState.py @@ -28,7 +28,7 @@ import Config class DbState(Callback): """ - Provide a class to encapsulate the state of the database.. + Provide a class to encapsulate the state of the database. """ __signals__ = { diff --git a/src/GrampsLogger/_ErrorReportAssistant.py b/src/GrampsLogger/_ErrorReportAssistant.py index df1b90797..89441f6d0 100644 --- a/src/GrampsLogger/_ErrorReportAssistant.py +++ b/src/GrampsLogger/_ErrorReportAssistant.py @@ -8,7 +8,7 @@ import sys, os,bsddb class ErrorReportAssistant(object): - def __init__(self,error_detail,rotate_handler): + def __init__(self,error_detail,rotate_handler, ownthread=False): self._error_detail = error_detail self._rotate_handler = rotate_handler @@ -18,6 +18,9 @@ class ErrorReportAssistant(object): self._final_report_text_buffer = None self.w = Assistant.Assistant(None,None,self.complete) + #connect our extra close to close by x, and close by cancel click + self.w.window.connect('delete-event', self.close) + self.w.cancel.connect('clicked', self.close) self.w.add_text_page( _('Report a bug'), @@ -52,13 +55,23 @@ class ErrorReportAssistant(object): self.w.connect('page-changed',self.on_page_changed) self.w.show() + + self.ownthread = ownthread + if self.ownthread: + gtk.main() + def close(self, *obj): + if self.ownthread: + gtk.main_quit() + def on_page_changed(self, obj,page,data=None): if page in self.cb: self.cb[page]() def complete(self): - pass + if self.ownthread: + #stop the thread we started + gtk.main_quit() def _copy_to_clipboard(self, obj=None): clipboard = gtk.Clipboard() @@ -75,7 +88,8 @@ class ErrorReportAssistant(object): def _start_email_client(self, obj=None): import GrampsDisplay - GrampsDisplay.url('mailto:gramps-bugs@lists.sourceforge.net?subject="bug report"&body="%s"' \ + GrampsDisplay.url('mailto:gramps-bugs@lists.sourceforge.net?subject=' + '"bug report"&body="%s"' \ % self._final_report_text_buffer.get_text( self._final_report_text_buffer.get_start_iter(), self._final_report_text_buffer.get_end_iter())) diff --git a/src/GrampsLogger/_ErrorView.py b/src/GrampsLogger/_ErrorView.py index cc9f0c1a1..3c9edfe4a 100644 --- a/src/GrampsLogger/_ErrorView.py +++ b/src/GrampsLogger/_ErrorView.py @@ -46,9 +46,12 @@ class ErrorView(object): if response == gtk.RESPONSE_HELP: self.help_clicked() elif response == gtk.RESPONSE_YES: + self.top.destroy() ErrorReportAssistant(error_detail = self._error_detail, - rotate_handler = self._rotate_handler) - self.top.destroy() + rotate_handler = self._rotate_handler, + ownthread=True) + elif response == gtk.RESPONSE_CANCEL: + self.top.destroy() def help_clicked(self): """Display the relevant portion of GRAMPS manual""" diff --git a/src/GrampsLogger/_GtkHandler.py b/src/GrampsLogger/_GtkHandler.py index 81b2bb94e..ee269c835 100644 --- a/src/GrampsLogger/_GtkHandler.py +++ b/src/GrampsLogger/_GtkHandler.py @@ -21,9 +21,7 @@ class GtkHandler(logging.Handler): def emit(self, record): """ Add the record to the rotating buffer. - """ - self._record = record ErrorView(error_detail=self,rotate_handler=self._rotate_handler) diff --git a/src/Makefile.am b/src/Makefile.am index 98d3fa4a3..853fdd35b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -3,6 +3,7 @@ SUBDIRS = \ BasicUtils \ + cli \ Config \ data \ DataViews \ @@ -18,6 +19,7 @@ SUBDIRS = \ GrampsDbUtils \ GrampsLocale \ GrampsLogger \ + gui \ images \ Merge \ mapstraction \ @@ -33,7 +35,6 @@ gdirdir=$(prefix)/share/gramps gdir_PYTHON = \ ansel_utf8.py\ - ArgHandler.py\ Assistant.py\ AutoComp.py\ Bookmarks.py\ @@ -41,8 +42,6 @@ gdir_PYTHON = \ const.py\ DateEdit.py\ Date.py\ - DbLoader.py\ - DbManager.py\ DbState.py\ DdTargets.py\ DisplayState.py\ @@ -52,7 +51,6 @@ gdir_PYTHON = \ GrampsAboutDialog.py\ GrampsCfg.py\ GrampsDisplay.py\ - gramps_main.py\ gramps.py\ ImgManip.py\ LdsUtils.py \ @@ -77,7 +75,6 @@ gdir_PYTHON = \ TransUtils.py\ TreeTips.py\ Utils.py\ - ViewManager.py\ UndoHistory.py\ PlaceUtils.py\ ProgressDialog.py diff --git a/src/cli/Makefile.am b/src/cli/Makefile.am index 70d6a4013..424da2228 100644 --- a/src/cli/Makefile.am +++ b/src/cli/Makefile.am @@ -6,7 +6,11 @@ pkgdatadir = $(datadir)/@PACKAGE@/cli pkgdata_PYTHON = \ - __init__.py + __init__.py \ + arghandler.py \ + argparser.py \ + clidbman.py \ + grampscli.py pkgpyexecdir = @pkgpyexecdir@/cli pkgpythondir = @pkgpythondir@/cli diff --git a/src/cli/__init__.py b/src/cli/__init__.py index f4f4e672b..c719604bb 100644 --- a/src/cli/__init__.py +++ b/src/cli/__init__.py @@ -22,3 +22,8 @@ """ Package init for the cli package. """ + +from grampscli import startcli, CLIDbLoader, CLIManager +from argparser import ArgParser +from arghandler import ArgHandler +from clidbman import CLIDbManager diff --git a/src/ArgHandler.py b/src/cli/arghandler.py similarity index 55% rename from src/ArgHandler.py rename to src/cli/arghandler.py index d23b930d2..2d5edbd43 100644 --- a/src/ArgHandler.py +++ b/src/cli/arghandler.py @@ -2,7 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2000-2006 Donald N. Allingham, A. Roitman -# Copyright (C) 2007-2008 B. Malengier +# Copyright (C) 2007-2009 B. Malengier # Copyright (C) 2008 Lukasz Rymarczyk # Copyright (C) 2008 Raphael Ackermann # Copyright (C) 2008 Brian G. Matherly @@ -22,7 +22,7 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # -# $Id$ +# $Id: ArgHandler.py 12559 2009-05-21 17:19:50Z gbritton $ """ Module responsible for handling the command line arguments for GRAMPS. @@ -49,186 +49,83 @@ import Config import RecentFiles import Utils import gen -from DbManager import CLIDbManager, NAME_FILE, find_locker_name +from clidbman import CLIDbManager, NAME_FILE, find_locker_name from PluginUtils import Tool from gen.plug import PluginManager from ReportBase import CATEGORY_BOOK, CATEGORY_CODE, cl_report -# Note: Make sure to edit const.py POPT_TABLE too! -_help = """ -Usage: gramps.py [OPTION...] - --load-modules=MODULE1,MODULE2,... Dynamic modules to load - -Help options - -?, --help Show this help message - --usage Display brief usage message - -Application options - -O, --open=FAMILY_TREE Open family tree - -i, --import=FILENAME Import file - -e, --export=FILENAME Export file - -f, --format=FORMAT Specify format - -a, --action=ACTION Specify action - -p, --options=OPTIONS_STRING Specify options - -d, --debug=LOGGER_NAME Enable debug logs - -l List Family Trees - -L List Family Tree Details - -u, --force-unlock Force unlock of family tree -""" - #------------------------------------------------------------------------- # ArgHandler #------------------------------------------------------------------------- class ArgHandler(object): """ - This class is responsible for handling command line arguments (if any) - given to gramps. The valid arguments are: - - FAMTREE : family tree name or database dir to open. - All following arguments will be ignored. - -O, --open=FAMTREE : Family tree or family tree database dir to open. - -i, --import=FILE : filename to import. - -e, --export=FILE : filename to export. - -f, --format=FORMAT : format of the file preceding this option. - - If the filename (no flags) is specified, the interactive session is - launched using data from filename. - In this mode (filename, no flags), the rest of the arguments is ignored. - This is a mode suitable by default for GUI launchers, mime type handlers, - and the like - - If no filename or -i option is given, a new interactive session (empty - database) is launched, since no data is given anyway. - - If -O or -i option is given, but no -e or -a options are given, an - interactive session is launched with the FILE (specified with -i). - - If both input (-O or -i) and processing (-e or -a) options are given, - interactive session will not be launched. + This class is responsible for the non GUI handling of commands + The handler is passed a parser object, sanitizes it, and can execute the + actions requested working on a DbState """ - def __init__(self, state, vm, args): - self.state = state - self.vm = vm - self.args = args - - self.open_gui = None - self.open = None + def __init__(self, dbstate, parser, sessionmanager, + errorfunc=None, gui=False): + self.dbstate = dbstate + self.sm = sessionmanager + self.errorfunc = errorfunc + self.gui = gui + self.dbman = CLIDbManager(self.dbstate) + self.force_unlock = parser.force_unlock + self.open = self.__handle_open_option(parser.open) self.cl = 0 - self.exports = [] - self.actions = [] self.imports = [] + self.exports = [] + self.sanitize_args(parser.imports, parser.exports) + self.open_gui = parser.open_gui + if self.gui: + self.actions = [] + self.list = False + self.list_more = False + self.open_gui = None + else: + self.actions = parser.actions + self.list = parser.list + self.list_more = parser.list_more self.imp_db_path = None - self.list = False - self.list_more = False - self.help = False - self.force_unlock = False - self.dbman = CLIDbManager(self.state) - - self.parse_args() + + def error(self, string): + if self.errorfunc: + self.errorfunc(string) + else: + print string #------------------------------------------------------------------------- # Argument parser: sorts out given arguments #------------------------------------------------------------------------- - def parse_args(self): + def sanitize_args(self, importlist, exportlist): """ - Fill in lists with open, exports, imports, and actions options. - - Any parsing errors lead to abort. - - Possible: - 1/ Just the family tree (name or database dir) - 2/ -O, Open of a family tree - 3/ -i, Import of any format understood by an importer, optionally provide - -f to indicate format - 4/ -e, export a family tree in required format, optionally provide - -f to indicate format - 5/ -a, --action: An action (possible: 'check', 'summary', 'report', - 'tool') - 6/ -u, --force-unlock: A locked database can be unlocked by given this - argument when opening it - + check the lists with open, exports, imports, and actions options. """ - try: - options, leftargs = getopt.getopt(self.args[1:], - const.SHORTOPTS, const.LONGOPTS) - except getopt.GetoptError, msg: - print msg - # return without filling anything if we could not parse the args - print "Error parsing the arguments: %s " % self.args[1:] - print "Type gramps --help for an overview of commands, or ", - print "read manual pages." - sys.exit(0) - - if leftargs: - # if there were an argument without option, - # use it as a file to open and return - self.open_gui = leftargs[0] - print "Trying to open: %s ..." % leftargs[0] - #see if force open is on - for opt_ix in range(len(options)): - option, value = options[opt_ix] - if option in ('-u', '--force-unlock'): - self.force_unlock = True - break - return - - # Go over all given option and place them into appropriate lists - for opt_ix in range(len(options)): - option, value = options[opt_ix] - if option in ( '-O', '--open'): - self.__handle_open_option(value) - elif option in ( '-i', '--import'): - format = None - if opt_ix < len(options) - 1 \ - and options[opt_ix + 1][0] in ( '-f', '--format'): - format = options[opt_ix + 1][1] - self.__handle_import_option(value, format) - elif option in ( '-e', '--export' ): - format = None - if opt_ix < len(options) - 1 \ - and options[opt_ix + 1][0] in ( '-f', '--format'): - format = options[opt_ix + 1][1] - self.__handle_export_option(value, format) - elif option in ( '-a', '--action' ): - action = value - if action not in ( 'check', 'summary', 'report', 'tool' ): - print "Unknown action: %s. Ignoring." % action - continue - options_str = "" - if opt_ix < len(options)-1 \ - and options[opt_ix+1][0] in ( '-p', '--options' ): - options_str = options[opt_ix+1][1] - self.actions.append((action, options_str)) - elif option in ('-d', '--debug'): - logger = logging.getLogger(value) - logger.setLevel(logging.DEBUG) - elif option in ('-l',): - self.list = True - elif option in ('-L',): - self.list_more = True - elif option in ('-h', '-?', '--help'): - self.help = True - elif option in ('-u', '--force-unlock'): - self.force_unlock = True + for (value, format) in importlist: + self.__handle_import_option(value, format) + for (value, format) in exportlist: + self.__handle_export_option(value, format) def __handle_open_option(self, value): """ Handle the "-O" or "--open" option. """ + if value is None: + return None db_path = self.__deduce_db_path(value) if db_path: # We have a potential database path. # Check if it is good. - if not self.__check_db(db_path, self.force_unlock): + if not self.check_db(db_path, self.force_unlock): sys.exit(0) - self.open = db_path + return db_path else: - print _('Input family tree "%s" does not exist.') % value - print _("If gedcom, gramps-xml or grdb, use the -i option to " - "import into a family tree instead") + self.error( _('Error: Input family tree "%s" does not exist.\n' + "If gedcom, gramps-xml or grdb, use the -i option to " + "import into a family tree instead.") % value) sys.exit(0) def __handle_import_option(self, value, format): @@ -238,9 +135,8 @@ class ArgHandler(object): fname = value fullpath = os.path.abspath(os.path.expanduser(fname)) if not os.path.exists(fullpath): - print 'Import file not found.' - print "Ignoring import file: %s" % fname - return + self.error(_('Error: Import file %s not found.') % fname) + sys.exit(0) if format is None: # Guess the file format based on the file extension. @@ -257,27 +153,33 @@ class ArgHandler(object): if plugin_found: self.imports.append((fname, format)) else: - print 'Unrecognized type: "%s" for import file: %s' \ - % (format, fname) - print "Ignoring import file: %s" % fname + self.error(_('Error: Unrecognized type: "%(format)s" for ' + 'import file: %(filename)s') \ + % {'format' : format, + 'filename' : fname}) + sys.exit(0) def __handle_export_option(self, value, format): """ - Handle the "-e" or "--export" option. + Handle the "-e" or "--export" option. + Note: only the CLI version has export """ + if self.gui: + return fname = value fullpath = os.path.abspath(os.path.expanduser(fname)) if os.path.exists(fullpath): - print "WARNING: Output file already exist!" - print "WARNING: It will be overwritten:\n %s" % fullpath + self.error(_("WARNING: Output file already exist!\n" + "WARNING: It will be overwritten:\n %(name)s") % \ + {'name' : fullpath}) answer = None while not answer: - answer = raw_input('OK to overwrite? (yes/no) ') - if answer.upper() in ('Y','YES'): - print "Will overwrite the existing file: %s" % fullpath + answer = raw_input(_('OK to overwrite? (yes/no) ')) + if answer.upper() in ('Y','YES', _('YES')): + self.error( _("Will overwrite the existing file: %s") + % fullpath) else: - print "Will skip the output file: %s" % fullpath - return + sys.exit(0) if format is None: # Guess the file format based on the file extension. @@ -294,8 +196,9 @@ class ArgHandler(object): if plugin_found: self.exports.append((fullpath, format)) else: - print "Unrecognized format for export file %s" % fname - print "Ignoring export file: %s" % fname + self.error(_("ERROR: Unrecognized format for export file %s") + % fname) + sys.exit(0) def __deduce_db_path(self, db_name_or_path): """ @@ -318,38 +221,60 @@ class ArgHandler(object): db_path = fullpath return db_path - - #------------------------------------------------------------------------- - # Determine the need for GUI - #------------------------------------------------------------------------- - def need_gui(self): - """ - Determine whether we need a GUI session for the given tasks. - """ - if self.open_gui: - # No-option argument, definitely GUI - return True - # If we have data to work with: - if (self.open or self.imports): - if (self.exports or self.actions): - # have both data and what to do with it => no GUI - return False - else: - # data given, but no action/export => GUI - return True - - # No data, can only do GUI here - return True - #------------------------------------------------------------------------- # Overall argument handler: # sorts out the sequence and details of operations #------------------------------------------------------------------------- - def handle_args(self): + def handle_args_gui(self, dbman): + """ + method to handle the arguments that can be given for a GUI session. + Returns the filename of the family tree that should be openend + 1/no options: a family tree can be given, if so, this name is tested + and returned. If a filename, it is imported in a new db + and name of new db returned + 2/an open option can have been given + + """ + if self.open_gui: + # First check if a Gramps database was provided + # (either a database path or a database name) + db_path = self.__deduce_db_path(self.open_gui) + + if not db_path: + # Apparently it is not a database. See if it is a file that + # can be imported. + db_path, title = self.dbman.import_new_db(self.open_gui, None) + + if db_path: + # Test if not locked or problematic + if not self.check_db(db_path, self.force_unlock): + sys.exit(0) + # Add the file to the recent items + path = os.path.join(db_path, "name.txt") + try: + ifile = open(path) + title = ifile.readline().strip() + ifile.close() + except: + title = db_path + RecentFiles.recent_files(db_path, title) + else: + sys.exit(0) + return db_path + + # if not open_gui, parse any command line args. We can only have one + # open argument, and perhaps some import arguments + self.__open_action() + self.__import_action() + + def handle_args_cli(self, climan): """ Depending on the given arguments, import or open data, launch session, write files, and/or perform actions. + + @param: climan: the manager of a CLI session + @type: CLIManager object """ if self.list: @@ -367,104 +292,70 @@ class ArgHandler(object): if item != "Family tree": print " %s: %s" % (item, summary[item]) sys.exit(0) - - if self.help: - print _help - sys.exit(0) - - if self.open_gui: - # First check if a Gramps database was provided - # (either a database path or a database name) - db_path = self.__deduce_db_path(self.open_gui) - - if not db_path: - # Apparently it is not a database. See if it is a file that - # can be imported. - db_path, title = self.dbman.import_new_db(self.open_gui, None) - - if db_path: - # Test if not locked or problematic - if not self.__check_db(db_path, self.force_unlock): - sys.exit(0) - # Add the file to the recent items - path = os.path.join(db_path, "name.txt") - try: - ifile = open(path) - title = ifile.readline().strip() - ifile.close() - except: - title = db_path - RecentFiles.recent_files(db_path, title) - else: - sys.exit(1) - return db_path - if self.open: - # Family Tree to open was given. Open it - # Then go on and process the rest of the command line arguments. - self.cl = bool(self.exports or self.actions) + self.__open_action() + self.__import_action() + + for (action, options_str) in self.actions: + print "Performing action: %s." % action + if options_str: + print "Using options string: %s" % options_str + self.cl_action(action, options_str) - filename = self.open + for expt in self.exports: + print "Exporting: file %s, format %s." % expt + self.cl_export(expt[0], expt[1]) - try: - self.vm.open_activate(filename) - print "Opened successfully!" - except: - print "Error opening the file." - print "Exiting..." - sys.exit(1) + print "Cleaning up." + # remove files in import db subdir after use + self.dbstate.db.close() + if self.imp_db_path: + Utils.rm_tempdir(self.imp_db_path) + print "Exiting." + sys.exit(0) + def __import_action(self): if self.imports: self.cl = bool(self.exports or self.actions or self.cl) if not self.open: # Create empty dir for imported database(s) - self.imp_db_path = Utils.get_empty_tempdir("import_dbdir") + if self.gui: + self.imp_db_path, title = self.dbman._create_new_db_cli() + else: + self.imp_db_path = Utils.get_empty_tempdir("import_dbdir") - newdb = gen.db.GrampsDBDir() - newdb.write_version(self.imp_db_path) + newdb = gen.db.GrampsDBDir() + newdb.write_version(self.imp_db_path) - if not self.vm.db_loader.read_file(self.imp_db_path): + try: + self.sm.open_activate(self.imp_db_path) + print "Created empty fam tree successfully" + except: + print "Error opening the file." + print "Exiting..." sys.exit(0) for imp in self.imports: print "Importing: file %s, format %s." % imp self.cl_import(imp[0], imp[1]) - elif len(self.args) > 1 and not self.open: - print "No data was given -- will launch interactive session." - print "To use in the command-line mode,", \ - "supply at least one input file to process." - print "Launching interactive session..." + def __open_action(self): + if self.open: + # Family Tree to open was given. Open it + # Then go on and process the rest of the command line arguments. + self.cl = bool(self.exports or self.actions) - if self.cl: - for (action, options_str) in self.actions: - print "Performing action: %s." % action - if options_str: - print "Using options string: %s" % options_str - self.cl_action(action, options_str) + # we load this file for use + try: + self.sm.open_activate(self.open) + print "Opened successfully!" + except: + print "Error opening the file." + print "Exiting..." + sys.exit(0) - for expt in self.exports: - print "Exporting: file %s, format %s." % expt - self.cl_export(expt[0], expt[1]) - - print "Cleaning up." - # remove files in import db subdir after use - self.state.db.close() - if self.imp_db_path: - Utils.rm_tempdir(self.imp_db_path) - print "Exiting." - sys.exit(0) - - elif Config.get(Config.RECENT_FILE) and Config.get(Config.AUTOLOAD): - filename = Config.get(Config.RECENT_FILE) - if os.path.isdir(filename) and \ - os.path.isfile(os.path.join(filename, "name.txt")) and \ - self.__check_db(filename): - self.vm.db_loader.read_file(filename) - return filename - - def __check_db(self, dbpath, force_unlock = False): + def check_db(self, dbpath, force_unlock = False): # Test if not locked or problematic if force_unlock: self.dbman.break_lock(dbpath) @@ -485,19 +376,18 @@ class ArgHandler(object): def cl_import(self, filename, format): """ Command-line import routine. Try to import filename using the format. - Any errors will cause the sys.exit(1) call. """ pmgr = PluginManager.get_instance() for plugin in pmgr.get_import_plugins(): if format == plugin.get_extension(): import_function = plugin.get_import_function() - import_function(self.state.db, filename, None) + import_function(self.dbstate.db, filename, None) if not self.cl: if self.imp_db_path: - return self.vm.open_activate(self.imp_db_path) + return self.sm.open_activate(self.imp_db_path) else: - return self.vm.open_activate(self.open) + return self.sm.open_activate(self.open) #------------------------------------------------------------------------- # @@ -508,13 +398,12 @@ class ArgHandler(object): """ Command-line export routine. Try to write into filename using the format. - Any errors will cause the sys.exit(1) call. """ pmgr = PluginManager.get_instance() for plugin in pmgr.get_export_plugins(): if format == plugin.get_extension(): export_function = plugin.get_export_function() - export_function(self.state.db, filename) + export_function(self.dbstate.db, filename) #------------------------------------------------------------------------- # @@ -528,7 +417,7 @@ class ArgHandler(object): pmgr = PluginManager.get_instance() if action == 'check': import Check - checker = Check.CheckIntegrity(self.state.db, None, None) + checker = Check.CheckIntegrity(self.dbstate.db, None, None) checker.check_for_broken_family_links() checker.cleanup_missing_photos(1) checker.check_parent_relationships() @@ -538,7 +427,7 @@ class ArgHandler(object): checker.report(1) elif action == 'summary': import Summary - text = Summary.build_report(self.state.db, None) + text = Summary.build_report(self.dbstate.db, None) print text elif action == "report": try: @@ -557,10 +446,10 @@ class ArgHandler(object): report_class = item[2] options_class = item[3] if category in (CATEGORY_BOOK, CATEGORY_CODE): - options_class(self.state.db, name, category, + options_class(self.dbstate.db, name, category, options_str_dict) else: - cl_report(self.state.db, name, category, report_class, + cl_report(self.dbstate.db, name, category, report_class, options_class, options_str_dict) return # name exists, but is not in the list of valid report names @@ -593,7 +482,7 @@ class ArgHandler(object): category = item[1] tool_class = item[2] options_class = item[3] - Tool.cli_tool(self.state, name, category, tool_class, + Tool.cli_tool(self.dbstate, name, category, tool_class, options_class, options_str_dict) return msg = "Unknown tool name." @@ -605,5 +494,4 @@ class ArgHandler(object): print " %s" % item[0] else: print "Unknown action: %s." % action - sys.exit(1) - + sys.exit(0) diff --git a/src/cli/argparser.py b/src/cli/argparser.py new file mode 100644 index 000000000..a7c5fd5f8 --- /dev/null +++ b/src/cli/argparser.py @@ -0,0 +1,256 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2006 Donald N. Allingham, A. Roitman +# Copyright (C) 2007-2009 B. Malengier +# Copyright (C) 2008 Lukasz Rymarczyk +# Copyright (C) 2008 Raphael Ackermann +# Copyright (C) 2008 Brian G. Matherly +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id: ArgHandler.py 12559 2009-05-21 17:19:50Z gbritton $ + +""" +Module responsible for handling the command line arguments for GRAMPS. +""" + +#------------------------------------------------------------------------- +# +# Standard python modules +# +#------------------------------------------------------------------------- +import os +import sys +import getopt +from gettext import gettext as _ +import logging + +#------------------------------------------------------------------------- +# +# gramps modules +# +#------------------------------------------------------------------------- +import const + + +# Note: Make sure to edit const.py POPT_TABLE too! +_HELP = _(""" +Usage: gramps.py [OPTION...] + --load-modules=MODULE1,MODULE2,... Dynamic modules to load + +Help options + -?, --help Show this help message + --usage Display brief usage message + +Application options + -O, --open=FAMILY_TREE Open family tree + -i, --import=FILENAME Import file + -e, --export=FILENAME Export file + -f, --format=FORMAT Specify format + -a, --action=ACTION Specify action + -p, --options=OPTIONS_STRING Specify options + -d, --debug=LOGGER_NAME Enable debug logs + -l List Family Trees + -L List Family Trees in Detail + -u, --force-unlock Force unlock of family tree +""") + +#------------------------------------------------------------------------- +# ArgParser +#------------------------------------------------------------------------- +class ArgParser(object): + """ + This class is responsible for parsing the command line arguments (if any) + given to gramps, and determining if a GUI or a CLI session must be started. + The valid arguments are: + + Possible: + 1/ FAMTREE : Just the family tree (name or database dir) + 2/ -O, --open=FAMTREE, Open of a family tree + 3/ -i, --import=FILE, Import of any format understood by an importer, optionally + provide- f to indicate format + 4/ -e, --export=FILE, export a family tree in required format, optionally provide + -f to indicate format + 5/ -f, --format=FORMAT : format after a -i or -e option + 5/ -a, --action: An action (possible: 'check', 'summary', 'report', + 'tool') + 6/ -u, --force-unlock: A locked database can be unlocked by giving this + argument when opening it + + If the filename (no flags) is specified, the interactive session is + launched using data from filename. + In this mode (filename, no flags), the rest of the arguments is ignored. + This is a mode suitable by default for GUI launchers, mime type handlers, + and the like + + If no filename or -i option is given, a new interactive session (empty + database) is launched, since no data is given anyway. + + If -O or -i option is given, but no -e or -a options are given, an + interactive session is launched with the FILE (specified with -i). + + If both input (-O or -i) and processing (-e or -a) options are given, + interactive session will not be launched. + """ + + def __init__(self, args): + """ + pass the command line arguments on creation + """ + self.args = args + + self.open_gui = None + self.open = None + self.exports = [] + self.actions = [] + self.imports = [] + self.imp_db_path = None + self.list = False + self.list_more = False + self.help = False + self.force_unlock = False + + self.errors = [] + self.parse_args() + + #------------------------------------------------------------------------- + # Argument parser: sorts out given arguments + #------------------------------------------------------------------------- + def parse_args(self): + """ + Fill in lists with open, exports, imports, and actions options. + + Any errors are added to self.errors + + Possible: + 1/ Just the family tree (name or database dir) + 2/ -O, Open of a family tree + 3/ -i, Import of any format understood by an importer, optionally + provide-f to indicate format + 4/ -e, export a family tree in required format, optionally provide + -f to indicate format + 5/ -a, --action: An action (possible: 'check', 'summary', 'report', + 'tool') + 6/ -u, --force-unlock: A locked database can be unlocked by giving this + argument when opening it + + """ + try: + options, leftargs = getopt.getopt(self.args[1:], + const.SHORTOPTS, const.LONGOPTS) + except getopt.GetoptError, msg: + self.errors += [(_('Error parsing the arguments'), + str(msg) + '\n' + + _("Error parsing the arguments: %s \n" + "Type gramps --help for an overview of commands, or " + "read the manual pages.") % self.args[1:])] + return + + if leftargs: + # if there were an argument without option, + # use it as a file to open and return + self.open_gui = leftargs[0] + print "Trying to open: %s ..." % leftargs[0] + #see if force open is on + for opt_ix in range(len(options)): + option, value = options[opt_ix] + if option in ('-u', '--force-unlock'): + self.force_unlock = True + break + return + + # Go over all given option and place them into appropriate lists + for opt_ix in range(len(options)): + option, value = options[opt_ix] + if option in ( '-O', '--open'): + self.open = value + elif option in ( '-i', '--import'): + format = None + if opt_ix < len(options) - 1 \ + and options[opt_ix + 1][0] in ( '-f', '--format'): + format = options[opt_ix + 1][1] + self.imports.append((value, format)) + elif option in ( '-e', '--export' ): + format = None + if opt_ix < len(options) - 1 \ + and options[opt_ix + 1][0] in ( '-f', '--format'): + format = options[opt_ix + 1][1] + self.exports.append((value, format)) + elif option in ( '-a', '--action' ): + action = value + if action not in ( 'check', 'summary', 'report', 'tool' ): + print "Unknown action: %s. Ignoring." % action + continue + options_str = "" + if opt_ix < len(options)-1 \ + and options[opt_ix+1][0] in ( '-p', '--options' ): + options_str = options[opt_ix+1][1] + self.actions.append((action, options_str)) + elif option in ('-d', '--debug'): + logger = logging.getLogger(value) + logger.setLevel(logging.DEBUG) + elif option in ('-l',): + self.list = True + elif option in ('-L',): + self.list_more = True + elif option in ('-h', '-?', '--help'): + self.help = True + elif option in ('-u', '--force-unlock'): + self.force_unlock = True + + if len(options) > 0 and self.open is None and self.imports == [] \ + and not (self.list or self.list_more or self.help): + self.errors += [(_('Error parsing the arguments'), + _("Error parsing the arguments: %s \n" + "To use in the command-line mode," \ + "supply at least one input file to process.") % self.args[1:])] + + #------------------------------------------------------------------------- + # Determine the need for GUI + #------------------------------------------------------------------------- + def need_gui(self): + """ + Determine whether we need a GUI session for the given tasks. + """ + if self.errors: + #errors in argument parsing ==> give cli error, no gui needed + return False + + if self.list or self.list_more or self.help: + return False + + if self.open_gui: + # No-option argument, definitely GUI + return True + + # If we have data to work with: + if (self.open or self.imports): + if (self.exports or self.actions): + # have both data and what to do with it => no GUI + return False + else: + # data given, but no action/export => GUI + return True + + # No data, can only do GUI here + return True + + def print_help(self): + if self.help: + print _HELP + sys.exit(0) + diff --git a/src/cli/clidbman.py b/src/cli/clidbman.py new file mode 100644 index 000000000..42102a96d --- /dev/null +++ b/src/cli/clidbman.py @@ -0,0 +1,380 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2009 Brian G. Matherly +# Copyright (C) 2009 Gary Burton +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id: DbManager.py 12621 2009-06-03 18:39:24Z ldnp $ + +""" +Provide the management of databases from CLI. This includes opening, renaming, +creating, and deleting of databases. +""" + +#------------------------------------------------------------------------- +# +# Standard python modules +# +#------------------------------------------------------------------------- +import os +import time +from gettext import gettext as _ +#------------------------------------------------------------------------- +# +# set up logging +# +#------------------------------------------------------------------------- +import logging +LOG = logging.getLogger(".clidbman") + +from gtk import STOCK_DIALOG_ERROR, STOCK_OPEN + +#------------------------------------------------------------------------- +# +# gramps modules +# +#------------------------------------------------------------------------- +import gen.db +from gen.plug import PluginManager +import Config + +#------------------------------------------------------------------------- +# +# constants +# +#------------------------------------------------------------------------- + +DEFAULT_TITLE = _("Family Tree") +NAME_FILE = "name.txt" +META_NAME = "meta_data.db" + +#------------------------------------------------------------------------- +# +# CLIDbManager +# +#------------------------------------------------------------------------- +class CLIDbManager(object): + """ + Database manager without GTK functionality, allows users to create and + open databases + """ + def __init__(self, dbstate): + self.dbstate = dbstate + self.msg = None + + if dbstate: + self.active = dbstate.db.get_save_path() + else: + self.active = None + + self.current_names = [] + self._populate_cli() + + def empty(self, val): + """Callback that does nothing + """ + pass + + def get_dbdir_summary(self, file_name): + """ + Returns (people_count, version_number) of current DB. + Returns ("Unknown", "Unknown") if invalid DB or other error. + """ + from bsddb import dbshelve, db + from gen.db import META, PERSON_TBL + env = db.DBEnv() + flags = db.DB_CREATE | db.DB_PRIVATE |\ + db.DB_INIT_MPOOL | db.DB_INIT_LOCK |\ + db.DB_INIT_LOG | db.DB_INIT_TXN | db.DB_THREAD + try: + env.open(file_name, flags) + except: + return "Unknown", "Unknown" + dbmap1 = dbshelve.DBShelf(env) + fname = os.path.join(file_name, META + ".db") + try: + dbmap1.open(fname, META, db.DB_HASH, db.DB_RDONLY) + except: + return "Unknown", "Unknown" + version = dbmap1.get('version', default=None) + dbmap1.close() + dbmap2 = dbshelve.DBShelf(env) + fname = os.path.join(file_name, PERSON_TBL + ".db") + try: + dbmap2.open(fname, PERSON_TBL, db.DB_HASH, db.DB_RDONLY) + except: + env.close() + return "Unknown", "Unknown" + count = len(dbmap2) + dbmap2.close() + env.close() + return (count, version) + + def family_tree_summary(self): + """ + Return a list of dictionaries of the known family trees. + """ + # make the default directory if it does not exist + list = [] + for item in self.current_names: + (name, dirpath, path_name, last, + tval, enable, stock_id) = item + count, version = self.get_dbdir_summary(dirpath) + retval = {} + retval["Number of people"] = count + if enable: + retval["Locked?"] = "yes" + else: + retval["Locked?"] = "no" + retval["DB version"] = version + retval["Family tree"] = name + retval["Path"] = dirpath + retval["Last accessed"] = time.strftime('%x %X', + time.localtime(tval)) + list.append( retval ) + return list + + def _populate_cli(self): + """ Get the list of current names in the database dir + """ + # make the default directory if it does not exist + dbdir = os.path.expanduser(Config.get(Config.DATABASE_PATH)) + make_dbdir(dbdir) + + self.current_names = [] + + for dpath in os.listdir(dbdir): + dirpath = os.path.join(dbdir, dpath) + path_name = os.path.join(dirpath, NAME_FILE) + if os.path.isfile(path_name): + name = file(path_name).readline().strip() + + (tval, last) = time_val(dirpath) + (enable, stock_id) = icon_values(dirpath, self.active, + self.dbstate.db.is_open()) + + if (stock_id == 'gramps-lock'): + last = find_locker_name(dirpath) + + self.current_names.append( + (name, os.path.join(dbdir, dpath), path_name, + last, tval, enable, stock_id)) + + self.current_names.sort() + + def get_family_tree_path(self, name): + """ + Given a name, return None if name not existing or the path to the + database if it is a known database name. + """ + for data in self.current_names: + if data[0] == name: + return data[1] + return None + + def family_tree_list(self): + """Return a list of name, dirname of the known family trees + """ + lst = [(x[0], x[1]) for x in self.current_names] + return lst + + def __start_cursor(self, msg): + """ + Do needed things to start import visually, eg busy cursor + """ + print _('Starting Import, %s') % msg + + def __end_cursor(self): + """ + Set end of a busy cursor + """ + print _('Import finished...') + + def _create_new_db_cli(self, title=None): + """ + Create a new database. + """ + new_path = find_next_db_dir() + + os.mkdir(new_path) + path_name = os.path.join(new_path, NAME_FILE) + + if title is None: + name_list = [ name[0] for name in self.current_names ] + title = find_next_db_name(name_list) + + name_file = open(path_name, "w") + name_file.write(title) + name_file.close() + + # write the version number into metadata + newdb = gen.db.GrampsDBDir() + newdb.write_version(new_path) + + (tval, last) = time_val(new_path) + + self.current_names.append((title, new_path, path_name, + last, tval, False, "")) + return new_path, title + + def _create_new_db(self, title=None): + """ + Create a new database, do extra stuff needed + """ + return self._create_new_db_cli(title) + + def import_new_db(self, filename, callback): + """ + Attempt to import the provided file into a new database. + A new database will only be created if an appropriate importer was + found. + + @return: A tuple of (new_path, name) for the new database + or (None, None) if no import was performed. + """ + pmgr = PluginManager.get_instance() + (name, ext) = os.path.splitext(os.path.basename(filename)) + format = ext[1:].lower() + + for plugin in pmgr.get_import_plugins(): + if format == plugin.get_extension(): + + new_path, name = self._create_new_db(name) + + # Create a new database + self.__start_cursor(_("Importing data...")) + dbclass = gen.db.GrampsDBDir + dbase = dbclass() + dbase.load(new_path, callback) + + import_function = plugin.get_import_function() + import_function(dbase, filename, callback) + + # finish up + self.__end_cursor() + dbase.close() + + return new_path, name + return None, None + + def is_locked(self, dbpath): + """ + returns True if there is a lock file in the dirpath + """ + if os.path.isfile(os.path.join(dbpath,"lock")): + return True + return False + + def needs_recovery(self, dbpath): + """ + returns True if the database in dirpath needs recovery + """ + if os.path.isfile(os.path.join(dbpath,"need_recover")): + return True + return False + + def break_lock(self, dbpath): + """ + Breaks the lock on a database + """ + if os.path.exists(os.path.join(dbpath, "lock")): + os.unlink(os.path.join(dbpath, "lock")) + +def make_dbdir(dbdir): + """ + Create the default database directory, as defined by dbdir + """ + try: + if not os.path.isdir(dbdir): + os.makedirs(dbdir) + except (IOError, OSError), msg: + LOG.error(_("Could not make database directory: ") + str(msg)) + +def find_next_db_name(name_list): + """ + Scan the name list, looking for names that do not yet exist. + Use the DEFAULT_TITLE as the basis for the database name. + """ + i = 1 + while True: + title = "%s %d" % (DEFAULT_TITLE, i) + if title not in name_list: + return title + i += 1 + +def find_next_db_dir(): + """ + Searches the default directory for the first available default + database name. Base the name off the current time. In all actuality, + the first should be valid. + """ + while True: + base = "%x" % int(time.time()) + dbdir = os.path.expanduser(Config.get(Config.DATABASE_PATH)) + new_path = os.path.join(dbdir, base) + if not os.path.isdir(new_path): + break + return new_path + +def time_val(dirpath): + """ + Return the last modified time of the database. We do this by looking + at the modification time of the meta db file. If this file does not + exist, we indicate that database as never modified. + """ + meta = os.path.join(dirpath, META_NAME) + if os.path.isfile(meta): + tval = os.stat(meta)[9] + last = time.strftime('%x %X', time.localtime(tval)) + else: + tval = 0 + last = _("Never") + return (tval, last) + +def icon_values(dirpath, active, is_open): + """ + If the directory path is the active path, then return values + that indicate to use the icon, and which icon to use. + """ + if os.path.isfile(os.path.join(dirpath,"need_recover")): + return (True, STOCK_DIALOG_ERROR) + elif dirpath == active and is_open: + return (True, STOCK_OPEN) + elif os.path.isfile(os.path.join(dirpath,"lock")): + return (True, 'gramps-lock') + else: + return (False, "") + +def find_locker_name(dirpath): + """ + Opens the lock file if it exists, reads the contexts which is "USERNAME" + and returns the contents, with correct string before "USERNAME", + so the message can be printed with correct locale. + If a file is encountered with errors, we return 'Unknown' + This data can eg be displayed in the time column of the manager + """ + try: + fname = os.path.join(dirpath, "lock") + ifile = open(fname) + username = ifile.read().strip() + last = _("Locked by %s") % username + ifile.close() + except (OSError, IOError): + last = _("Unknown") + return last diff --git a/src/cli/grampscli.py b/src/cli/grampscli.py new file mode 100644 index 000000000..75b3a1c5e --- /dev/null +++ b/src/cli/grampscli.py @@ -0,0 +1,284 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2001-2006 Donald N. Allingham +# Copyright (C) 2009 Benny Malengier +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id:gramps_main.py 9912 2008-01-22 09:17:46Z acraphae $ + +#------------------------------------------------------------------------- +# +# Python modules +# +#------------------------------------------------------------------------- +from gettext import gettext as _ +import os +import sys + +#------------------------------------------------------------------------- +# +# GRAMPS modules +# +#------------------------------------------------------------------------- +from BasicUtils import name_displayer +import Config +import const +import Errors +import DbState +from gen.db import GrampsDBDir +from gen.plug import PluginManager +import GrampsCfg +import RecentFiles + +#------------------------------------------------------------------------- +# +# CLI DbLoader class +# +#------------------------------------------------------------------------- +class CLIDbLoader(object): + def __init__(self, dbstate): + self.dbstate = dbstate + + def _warn(title, warnmessage): + print _('WARNING: %s') %warnmessage + + def _errordialog(title, errormessage): + """ + Show the error. A title for the error and an errormessage + """ + print _('ERROR: %s') % errormessage + sys.exit(1) + + def _dberrordialog(self, msg): + self._errordialog( '', _("Low level database corruption detected") + + '\n' + + _("GRAMPS has detected a problem in the underlying " + "Berkeley database. This can be repaired by from " + "the Family Tree Manager. Select the database and " + 'click on the Repair button') + '\n\n' + str(msg)) + + def _begin_progress(self): + pass + + def _pulse_progress(self, value): + pass + + def read_file(self, filename): + """ + This method takes care of changing database, and loading the data. + In 3.0 we only allow reading of real databases of filetype + 'x-directory/normal' + + This method should only return on success. + Returning on failure makes no sense, because we cannot recover, + since database has already beeen changed. + Therefore, any errors should raise exceptions. + + On success, return with the disabled signals. The post-load routine + should enable signals, as well as finish up with other UI goodies. + """ + + if os.path.exists(filename): + if not os.access(filename, os.W_OK): + mode = "r" + self._warn(_('Read only database'), + _('You do not have write access ' + 'to the selected file.')) + else: + mode = "w" + else: + mode = 'w' + + dbclass = GrampsDBDir + + self.dbstate.change_database(dbclass()) + self.dbstate.db.disable_signals() + + self._begin_progress() + + try: + self.dbstate.db.load(filename, self._pulse_progress, mode) + self.dbstate.db.set_save_path(filename) + except gen.db.FileVersionDeclineToUpgrade: + self.dbstate.no_database() + except gen.db.exceptions.FileVersionError, msg: + self.dbstate.no_database() + self._errordialog( _("Cannot open database"), str(msg)) + except OSError, msg: + self.dbstate.no_database() + self._errordialog( + _("Could not open file: %s") % filename, str(msg)) + except Errors.DbError, msg: + self.dbstate.no_database() + self._dberrordialog(msg) + except Exception: + self.dbstate.no_database() + _LOG.error("Failed to open database.", exc_info=True) + return True + +#------------------------------------------------------------------------- +# +# CLIManager class +# +#------------------------------------------------------------------------- + +class CLIManager(object): + """ + A reduced viewmanager suitable for cli actions. + Aim is to manage a database on which to work + """ + def __init__(self, dbstate, setloader): + self.dbstate = dbstate + if setloader: + self.db_loader = CLIDbLoader(self.dbstate) + else: + self.db_loader = None + self.file_loaded = False + self._pmgr = PluginManager.get_instance() + + def open_activate(self, path): + """ + Open and make a family tree active + """ + self._read_recent_file(path) + + def _errordialog(title, errormessage): + """ + Show the error. A title for the error and an errormessage + """ + print _('ERROR: %s') % errormessage + sys.exit(1) + + def _read_recent_file(self, filename): + """ + Called when a file needs to be loaded + """ + # A recent database should already have a directory + # If not, do nothing, just return. This can be handled better if family tree + # delete/rename also updated the recent file menu info in DisplayState.py + if not os.path.isdir(filename): + self.errordialog( + _("Could not load a recent Family Tree."), + _("Family Tree does not exist, as it has been deleted.")) + return + + if self.db_loader.read_file(filename): + # Attempt to figure out the database title + path = os.path.join(filename, "name.txt") + try: + ifile = open(path) + title = ifile.readline().strip() + ifile.close() + except: + title = filename + + self._post_load_newdb(filename, 'x-directory/normal', title) + + def _post_load_newdb(self, filename, filetype, title=None): + """ + The method called after load of a new database. + Here only CLI stuff is done, inherit this method to add extra stuff + """ + self._post_load_newdb_nongui(filename, filetype, title) + + def _post_load_newdb_nongui(self, filename, filetype, title=None): + """ + Called after a new database is loaded. + """ + if not filename: + return + + if filename[-1] == os.path.sep: + filename = filename[:-1] + name = os.path.basename(filename) + if title: + name = title + + # This method is for UI stuff when the database has changed. + # Window title, recent files, etc related to new file. + + self.dbstate.db.set_save_path(filename) + + # apply preferred researcher if loaded file has none + res = self.dbstate.db.get_researcher() + owner = GrampsCfg.get_researcher() + if res.get_name() == "" and owner.get_name() != "": + self.dbstate.db.set_researcher(owner) + + name_displayer.set_name_format(self.dbstate.db.name_formats) + fmt_default = Config.get(Config.NAME_FORMAT) + if fmt_default < 0: + name_displayer.set_default_format(fmt_default) + + self.dbstate.db.enable_signals() + self.dbstate.signal_change() + + Config.set(Config.RECENT_FILE, filename) + + try: + self.dbstate.change_active_person(self.dbstate.db.find_initial_person()) + except: + pass + + RecentFiles.recent_files(filename, name) + self.file_loaded = True + + def do_load_plugins(self): + """ + Loads the plugins at initialization time. The plugin status window is + opened on an error if the user has requested. + """ + # load plugins + + error = self._pmgr.load_plugins(const.PLUGINS_DIR) + error |= self._pmgr.load_plugins(const.USER_PLUGINS) + + return error + +def startcli(errors, argparser): + """ + Starts a cli session of GRAMPS. + errors : errors already encountered + argparser : ArgParser instance + """ + if errors: + #already errors encountered. Show first one on terminal and exit + print _('Error encountered: %s') % errors[0][0] + print _(' Details: %s') % errors[0][1] + sys.exit(1) + + if argparser.errors: + print _('Error encountered in argument parsing: %s') \ + % argparser.errors[0][0] + print _(' Details: %s') % argparser.errors[0][1] + sys.exit(1) + + #we need to keep track of the db state + dbstate = DbState.DbState() + #we need a manager for the CLI session + climanager = CLIManager(dbstate, True) + #load the plugins + climanager.do_load_plugins() + # handle the arguments + from arghandler import ArgHandler + handler = ArgHandler(dbstate, argparser, climanager) + # create a manager to manage the database + + handler.handle_args_cli(climanager) + + sys.exit(0) diff --git a/src/glade/Makefile.am b/src/glade/Makefile.am index 0e5e125a4..7d44bef31 100644 --- a/src/glade/Makefile.am +++ b/src/glade/Makefile.am @@ -23,7 +23,7 @@ dist_pkgdata_DATA = \ dateedit.glade \ editsource.glade \ styleeditor.glade \ - dbmanager.glade \ + dbman.glade \ editurl.glade \ editrepository.glade \ editreporef.glade \ diff --git a/src/glade/dbmanager.glade b/src/glade/dbman.glade similarity index 100% rename from src/glade/dbmanager.glade rename to src/glade/dbman.glade diff --git a/src/gramps.py b/src/gramps.py index 1f060ff52..58c1df5b6 100644 --- a/src/gramps.py +++ b/src/gramps.py @@ -2,6 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2000-2006 Donald N. Allingham +# Copyright (C) 2009 Benny Malengier # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -33,29 +34,14 @@ import signal import gettext import logging -log = logging.getLogger(".") +LOG = logging.getLogger(".") #------------------------------------------------------------------------- # -# pygtk +# GRAMPS modules # #------------------------------------------------------------------------- -try: - import pygtk - pygtk.require('2.0') -except ImportError: - pass - -#------------------------------------------------------------------------- -# -# Miscellaneous initialization -# -#------------------------------------------------------------------------- -import gtk -from gtk import glade -import gobject - -gobject.threads_init() +from Mime import mime_type_is_defined #------------------------------------------------------------------------- # @@ -78,13 +64,6 @@ except ValueError: pass gettext.bindtextdomain("gramps",loc) -glade.bindtextdomain("gramps",loc) - -try: - glade.textdomain("gramps") -except: - pass - gettext.textdomain("gramps") gettext.install("gramps",loc,unicode=1) @@ -117,24 +96,15 @@ except: args = sys.argv +MIN_PYTHON_VERSION = (2, 5, 0, '', 0) + def setup_logging(): """Setup basic logging support.""" - from GrampsLogger import RotateHandler, GtkHandler - # Setup a formatter form = logging.Formatter(fmt="%(relativeCreated)d: %(levelname)s: %(filename)s: line %(lineno)d: %(message)s") # Create the log handlers - rh = RotateHandler(capacity=20) - rh.setFormatter(form) - - # Only error and critical log records should - # trigger the GUI handler. - gtkh = GtkHandler(rotate_handler=rh) - gtkh.setFormatter(form) - gtkh.setLevel(logging.ERROR) - stderrh = logging.StreamHandler(sys.stderr) stderrh.setFormatter(form) stderrh.setLevel(logging.DEBUG) @@ -143,8 +113,6 @@ def setup_logging(): # everything. l = logging.getLogger() l.setLevel(logging.WARNING) - l.addHandler(rh) - l.addHandler(gtkh) l.addHandler(stderrh) # put a hook on to catch any completely unhandled exceptions. @@ -156,69 +124,53 @@ def setup_logging(): # strange Windows logging error on close return import traceback - log.error("Unhandled exception\n" + + LOG.error("Unhandled exception\n" + "".join(traceback.format_exception(type, value, tb))) sys.excepthook = exc_hook +def build_user_paths(): + """ check/make user-dirs on each Gramps session""" + for path in const.USER_DIRLIST: + if not os.path.isdir(path): + os.mkdir(path) + def run(): - - setup_logging() - - try: - #This is GNOME initialization code that is necessary for use - # with the other GNOME libraries. - #It only gets called if the user has gnome installed on his/her system. - #There is *no* requirement for it. - #If you don't call this, you are not guaranteed that the other GNOME - #libraries will function properly. I learned this the hard way. - import gnome - program = gnome.program_init('gramps',const.VERSION, - gnome.libgnome_module_info_get(), - args, const.POPT_TABLE) + error = [] - program.set_property('app-libdir', - '%s/lib' % const.PREFIXDIR) - program.set_property('app-datadir', - '%s/share' % const.PREFIXDIR) - program.set_property('app-sysconfdir',const.SYSCONFDIR) - program.set_property('app-prefix', const.PREFIXDIR) + setup_logging() + + try: + build_user_paths() + except OSError, msg: + error += [(_("Configuration error"), str(msg))] + return False except: - pass - - try: - quit_now = False - exit_code = 0 - import gramps_main - gramps_main.Gramps(args) - # TODO: check for returns from gramps_main.Gramps.__init__() - # that perhaps should have raised an exception to be caught here - - except SystemExit, e: - quit_now = True - if e.code: - exit_code = e.code - log.error("Gramps terminated with exit code: %d." \ - % e.code, exc_info=True) - except OSError, e: - quit_now = True - exit_code = e[0] or 1 - try: - fn = e.filename - except AttributeError: - fn = "" - log.error("Gramps terminated because of OS Error\n" + - "Error details: %s %s" % (repr(e), fn), exc_info=True) - except: - quit_now = True - exit_code = 1 - log.error("Gramps failed to start.", exc_info=True) - - if quit_now: - gtk.main_quit() - sys.exit(exit_code) + LOG.error("Error reading configuration.", exc_info=True) + return False - return False + if not mime_type_is_defined(const.APP_GRAMPS): + error += [(_("Configuration error"), + _("A definition for the MIME-type %s could not " + "be found \n\n Possibly the installation of GRAMPS " + "was incomplete. Make sure the MIME-types " + "of GRAMPS are properly installed.") + % const.APP_GRAMPS)] + + #we start with parsing the arguments to determine if we have a cli or a + # gui session + from cli import ArgParser + argpars = ArgParser(sys.argv) + + if argpars.need_gui(): + #A GUI is needed, set it up + from gui import startgtkloop + startgtkloop(error, argpars) + else: + #CLI use of GRAMPS + argpars.print_help() -gobject.timeout_add(100, run, priority=100) -gtk.main() + from cli import startcli + startcli(error, argpars) + +run() diff --git a/src/gramps_main.py b/src/gramps_main.py deleted file mode 100644 index fe93218b0..000000000 --- a/src/gramps_main.py +++ /dev/null @@ -1,292 +0,0 @@ -# -# Gramps - a GTK+/GNOME based genealogy program -# -# Copyright (C) 2001-2006 Donald N. Allingham -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -# - -# $Id:gramps_main.py 9912 2008-01-22 09:17:46Z acraphae $ - -#------------------------------------------------------------------------- -# -# Python modules -# -#------------------------------------------------------------------------- -from gettext import gettext as _ -import os -import platform - -import logging -log = logging.getLogger(".") - -#------------------------------------------------------------------------- -# -# GTK+/GNOME modules -# -#------------------------------------------------------------------------- -import gtk - -#------------------------------------------------------------------------- -# -# GRAMPS modules -# -#------------------------------------------------------------------------- -import ViewManager -import ArgHandler -import Config -import const -import Errors -import TipOfDay -import DataViews -import DbState -from Mime import mime_type_is_defined -from QuestionDialog import ErrorDialog - -#------------------------------------------------------------------------- -# -# helper functions -# -#------------------------------------------------------------------------- - - -def register_stock_icons (): - """ - Add the gramps names for its icons (eg gramps-person) to the GTK icon - factory. This allows all gramps modules to call up the icons by their name - """ - - #iconpath to the base image. The front of the list has highest priority - if platform.system() == "Windows": - iconpaths = [ - (os.path.join(const.IMAGE_DIR, '48x48'), '.png'), - (const.IMAGE_DIR, '.png'), - ] - else : - iconpaths = [ - (os.path.join(const.IMAGE_DIR, 'scalable'), '.svg'), - (const.IMAGE_DIR, '.svg'), (const.IMAGE_DIR, '.png'), - ] - - #sizes: menu=16, small_toolbar=18, large_toolbar=24, - # button=20, dnd=32, dialog=48 - #add to the back of this list to overrule images set at beginning of list - extraiconsize = [ - (os.path.join(const.IMAGE_DIR, '22x22'), - gtk.ICON_SIZE_LARGE_TOOLBAR), - (os.path.join(const.IMAGE_DIR, '16x16'), - gtk.ICON_SIZE_MENU), - (os.path.join(const.IMAGE_DIR, '22x22'), - gtk.ICON_SIZE_BUTTON), - ] - - items = [ - ('gramps-db', _('Family Trees'), gtk.gdk.CONTROL_MASK, 0, ''), - ('gramps-address', _('Address'), gtk.gdk.CONTROL_MASK, 0, ''), - ('gramps-attribute', _('Attribute'), gtk.gdk.CONTROL_MASK, 0, ''), - #('gramps-bookmark', _('Bookmarks'), gtk.gdk.CONTROL_MASK, 0, ''), - #('gramps-bookmark-delete', _('Delete bookmark'), gtk.gdk.CONTROL_MASK, 0, ''), - ('gramps-bookmark-edit', _('Organize Bookmarks'), gtk.gdk.CONTROL_MASK, 0, ''), - ('gramps-bookmark-new', _('Add Bookmark'), gtk.gdk.CONTROL_MASK, 0, ''), - ('gramps-date', _('Date'), gtk.gdk.CONTROL_MASK, 0, ''), - ('gramps-date-edit', _('Edit Date'), gtk.gdk.CONTROL_MASK, 0, ''), - ('gramps-event', _('Events'), gtk.gdk.CONTROL_MASK, 0, ''), - ('gramps-family', _('Family'), gtk.gdk.CONTROL_MASK, 0, ''), - ('gramps-font', _('Font'), gtk.gdk.CONTROL_MASK, 0, ''), - ('gramps-font-color', _('Font Color'), gtk.gdk.CONTROL_MASK, 0, ''), - ('gramps-font-bgcolor', _('Font Background Color'), gtk.gdk.CONTROL_MASK, 0, ''), - ('gramps-gramplet', _('Gramplets'), gtk.gdk.CONTROL_MASK, 0, ''), - ('gramps-geo', _('GeoView'), gtk.gdk.CONTROL_MASK, 0, ''), - ('gramps-lock', _('Public'), gtk.gdk.CONTROL_MASK, 0, ''), - ('gramps-media', _('Media'), gtk.gdk.CONTROL_MASK, 0, ''), - ('gramps-notes', _('Notes'), gtk.gdk.CONTROL_MASK, 0, ''), - ('gramps-parents', _('Parents'), gtk.gdk.CONTROL_MASK, 0, ''), - ('gramps-parents-add', _('Add Parents'), gtk.gdk.CONTROL_MASK, 0, ''), - ('gramps-parents-open', _('Select Parents'), gtk.gdk.CONTROL_MASK, 0, ''), - ('gramps-pedigree', _('Pedigree'), gtk.gdk.CONTROL_MASK, 0, ''), - ('gramps-person', _('Person'), gtk.gdk.CONTROL_MASK, 0, ''), - ('gramps-place', _('Places'), gtk.gdk.CONTROL_MASK, 0, ''), - ('gramps-relation', _('Relationships'), gtk.gdk.CONTROL_MASK, 0, ''), - ('gramps-reports', _('Reports'), gtk.gdk.CONTROL_MASK, 0, ''), - ('gramps-repository', _('Repositories'), gtk.gdk.CONTROL_MASK, 0, ''), - ('gramps-source', _('Sources'), gtk.gdk.CONTROL_MASK, 0, ''), - ('gramps-spouse', _('Add Spouse'), gtk.gdk.CONTROL_MASK, 0, ''), - ('gramps-tools', _('Tools'), gtk.gdk.CONTROL_MASK, 0, ''), - ('gramps-unlock', _('Private'), gtk.gdk.CONTROL_MASK, 0, ''), - ('gramps-viewmedia', _('View'), gtk.gdk.CONTROL_MASK, 0, ''), - ('gramps-zoom-in', _('Zoom In'), gtk.gdk.CONTROL_MASK, 0, ''), - ('gramps-zoom-out', _('Zoom Out'), gtk.gdk.CONTROL_MASK, 0, ''), - ('gramps-zoom-fit-width', _('Fit Width'), gtk.gdk.CONTROL_MASK, 0, ''), - ('gramps-zoom-best-fit', _('Fit Page'), gtk.gdk.CONTROL_MASK, 0, ''), - ] - # the following icons are not yet in new directory structure - # they should be ported in the near future - items_legacy = [ - ('gramps-export', _('Export'), gtk.gdk.CONTROL_MASK, 0, ''), - ('gramps-import', _('Import'), gtk.gdk.CONTROL_MASK, 0, ''), - ('gramps-undo-history', _('Undo History'), gtk.gdk.CONTROL_MASK, 0, ''), - ('gramps-url', _('URL'), gtk.gdk.CONTROL_MASK, 0, ''), - ] - - # Register our stock items - gtk.stock_add (items+items_legacy) - - # Add our custom icon factory to the list of defaults - factory = gtk.IconFactory () - factory.add_default () - - for data in items+items_legacy: - pixbuf = 0 - for (dirname, ext) in iconpaths: - icon_file = os.path.expanduser(os.path.join(dirname, data[0]+ext)) - if os.path.isfile(icon_file): - try: - pixbuf = gtk.gdk.pixbuf_new_from_file (icon_file) - break - except: - pass - - if not pixbuf : - icon_file = os.path.join(const.IMAGE_DIR, 'gramps.png') - pixbuf = gtk.gdk.pixbuf_new_from_file (icon_file) - - pixbuf = pixbuf.add_alpha(True, chr(0xff), chr(0xff), chr(0xff)) - icon_set = gtk.IconSet (pixbuf) - #add different sized icons, always png type! - for size in extraiconsize : - pixbuf = 0 - icon_file = os.path.expanduser( - os.path.join(size[0], data[0]+'.png')) - if os.path.isfile(icon_file): - try: - pixbuf = gtk.gdk.pixbuf_new_from_file (icon_file) - except: - pass - - if pixbuf : - source = gtk.IconSource() - source.set_size_wildcarded(False) - source.set_size(size[1]) - source.set_pixbuf(pixbuf) - icon_set.add_source(source) - - factory.add (data[0], icon_set) - - -def build_user_paths(): - """ check/make user-dirs on each Gramps session""" - for path in const.USER_DIRLIST: - if not os.path.isdir(path): - os.mkdir(path) - -def _display_welcome_message(): - """ - Display a welcome message to the user. - """ - if not Config.get(Config.BETAWARN): - from QuestionDialog import WarningDialog - WarningDialog( - _('Danger: This is unstable code!'), - _("This GRAMPS 3.x-trunk is a development release. " - "This version is not meant for normal usage. Use " - "at your own risk.\n" - "\n" - "This version may:\n" - "1) Work differently than you expect.\n" - "2) Fail to run at all.\n" - "3) Crash often.\n" - "4) Corrupt your data.\n" - "5) Save data in a format that is incompatible with the " - "official release.\n" - "\n" - "BACKUP your existing databases before opening " - "them with this version, and make sure to export your " - "data to XML every now and then.")) - Config.set(Config.AUTOLOAD, False) -# Config.set(Config.BETAWARN, True) - Config.set(Config.BETAWARN, Config.get(Config.BETAWARN)) - -#------------------------------------------------------------------------- -# -# Main Gramps class -# -#------------------------------------------------------------------------- -class Gramps(object): - """ - Main class corresponding to a running gramps process. - - There can be only one instance of this class per gramps application - process. It may spawn several windows and control several databases. - """ - - def __init__(self, args): - stopload = False - try: - build_user_paths() - _display_welcome_message() - except OSError, msg: - ErrorDialog(_("Configuration error"), str(msg)) - except Errors.GConfSchemaError, val: - ErrorDialog(_("Configuration error"), str(val) + - _("\n\nPossibly the installation of GRAMPS " - "was incomplete. Make sure the GConf schema " - "of GRAMPS is properly installed.")) - gtk.main_quit() - stopload = True - except: - log.error("Error reading configuration.", exc_info=True) - - if not mime_type_is_defined(const.APP_GRAMPS): - ErrorDialog(_("Configuration error"), - _("A definition for the MIME-type %s could not " - "be found \n\nPossibly the installation of GRAMPS " - "was incomplete. Make sure the MIME-types " - "of GRAMPS are properly installed.") - % const.APP_GRAMPS) - gtk.main_quit() - stopload = True - - register_stock_icons() - - state = DbState.DbState() - self.vm = ViewManager.ViewManager(state) - for view in DataViews.get_views(): - self.vm.register_view(view) - - if stopload: - # We stop further loading so family tree manager is not shown - # before the exit of GRAMPS - return - - self.vm.init_interface() - - # Depending on the nature of this session, - # we may need to change the order of operation - ah = ArgHandler.ArgHandler(state, self.vm, args) - if ah.need_gui(): - filename = ah.handle_args() - if filename: - self.vm.post_init_interface(show_manager=False) - self.vm.open_activate(filename) - else: - self.vm.post_init_interface() - else: - ah.handle_args() - self.vm.post_init_interface() - - if Config.get(Config.USE_TIPS): - TipOfDay.TipOfDay(self.vm.uistate) - - diff --git a/src/gui/Makefile.am b/src/gui/Makefile.am index 5b899f28c..bc17f7e6f 100644 --- a/src/gui/Makefile.am +++ b/src/gui/Makefile.am @@ -9,7 +9,11 @@ SUBDIRS = \ pkgdatadir = $(datadir)/@PACKAGE@/gui pkgdata_PYTHON = \ - __init__.py + __init__.py \ + dbloader.py \ + dbman.py \ + grampsgui.py \ + viewmanager.py pkgpyexecdir = @pkgpyexecdir@/gui pkgpythondir = @pkgpythondir@/gui diff --git a/src/gui/__init__.py b/src/gui/__init__.py index 59361d5ab..9431b8141 100644 --- a/src/gui/__init__.py +++ b/src/gui/__init__.py @@ -24,4 +24,9 @@ Package init for the gui package. """ +from grampsgui import startgtkloop +from viewmanager import ViewManager +from dbman import DbManager +from dbloader import DbLoader + __all__ = [ "views" ] diff --git a/src/DbLoader.py b/src/gui/dbloader.py similarity index 85% rename from src/DbLoader.py rename to src/gui/dbloader.py index 0c018a1a1..225fa9330 100644 --- a/src/DbLoader.py +++ b/src/gui/dbloader.py @@ -54,6 +54,7 @@ import gobject # GRAMPS modules # #------------------------------------------------------------------------- +from cli.grampscli import CLIDbLoader import const import Config import gen.db @@ -68,11 +69,32 @@ import Errors # DbLoader class # #------------------------------------------------------------------------- -class DbLoader(object): +class DbLoader(CLIDbLoader): def __init__(self, dbstate, uistate): - self.dbstate = dbstate + CLIDbLoader.__init__(self, dbstate) self.uistate = uistate self.import_info = None + + def _warn(title, warnmessage): + WarningDialog(title, warnmessage) + + def _errordialog(title, errormessage): + """ + Show the error. + In the GUI, the error is shown, and a return happens + """ + ErrorDialog(title, errormessage) + return 1 + + def _dberrordialog(self, msg): + DBErrorDialog(str(msg.value)) + + def _begin_progress(self): + self.uistate.window.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH)) + self.uistate.progress.show() + + def _pulse_progress(self, value): + self.uistate.pulse_progressbar(value) def import_file(self): # First thing first: import is a batch transaction @@ -198,58 +220,6 @@ class DbLoader(object): return False - def read_file(self, filename): - """ - This method takes care of changing database, and loading the data. - In 3.0 we only allow reading of real databases of filetype - 'x-directory/normal' - - This method should only return on success. - Returning on failure makes no sense, because we cannot recover, - since database has already beeen changed. - Therefore, any errors should raise exceptions. - - On success, return with the disabled signals. The post-load routine - should enable signals, as well as finish up with other UI goodies. - """ - - if os.path.exists(filename): - if not os.access(filename, os.W_OK): - mode = "r" - WarningDialog(_('Read only database'), - _('You do not have write access ' - 'to the selected file.')) - else: - mode = "w" - else: - mode = 'w' - - dbclass = gen.db.GrampsDBDir - - self.dbstate.change_database(dbclass()) - self.dbstate.db.disable_signals() - - self.uistate.window.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH)) - self.uistate.progress.show() - - try: - self.dbstate.db.load(filename, self.uistate.pulse_progressbar, mode) - self.dbstate.db.set_save_path(filename) - except gen.db.FileVersionDeclineToUpgrade: - self.dbstate.no_database() - except gen.db.exceptions.FileVersionError, msg: - ErrorDialog( _("Cannot open database"), str(msg)) - self.dbstate.no_database() - except OSError, msg: - ErrorDialog( - _("Could not open file: %s") % filename, str(msg)) - except Errors.DbError, msg: - DBErrorDialog(str(msg.value)) - self.dbstate.no_database() - except Exception: - _LOG.error("Failed to open database.", exc_info=True) - return True - def do_import(self, dialog, importer, filename): self.import_info = None dialog.destroy() diff --git a/src/DbManager.py b/src/gui/dbman.py similarity index 74% rename from src/DbManager.py rename to src/gui/dbman.py index 282f2f24b..b2f0a2dc3 100644 --- a/src/DbManager.py +++ b/src/gui/dbman.py @@ -71,6 +71,7 @@ import const from QuestionDialog import ErrorDialog, QuestionDialog import gen.db from gen.plug import PluginManager +from cli.clidbman import * import GrampsDbUtils import Config import Mime @@ -87,9 +88,7 @@ _KP_ENTER = gtk.gdk.keyval_from_name("KP_Enter") # constants # #------------------------------------------------------------------------- -DEFAULT_TITLE = _("Family Tree") -NAME_FILE = "name.txt" -META_NAME = "meta_data.db" + ARCHIVE = "rev.gramps" ARCHIVE_V = "rev.gramps,v" @@ -103,233 +102,6 @@ STOCK_COL = 6 RCS_BUTTON = { True : _('_Extract'), False : _('_Archive') } -class CLIDbManager(object): - """ - Database manager without GTK functionality, allows users to create and - open databases - """ - def __init__(self, dbstate): - self.dbstate = dbstate - self.msg = None - - if dbstate: - self.active = dbstate.db.get_save_path() - else: - self.active = None - - self.current_names = [] - self._populate_cli() - - def empty(self, val): - """Callback that does nothing - """ - pass - - def get_dbdir_summary(self, file_name): - """ - Returns (people_count, version_number) of current DB. - Returns ("Unknown", "Unknown") if invalid DB or other error. - """ - from bsddb import dbshelve, db - from gen.db import META, PERSON_TBL - env = db.DBEnv() - flags = db.DB_CREATE | db.DB_PRIVATE |\ - db.DB_INIT_MPOOL | db.DB_INIT_LOCK |\ - db.DB_INIT_LOG | db.DB_INIT_TXN | db.DB_THREAD - try: - env.open(file_name, flags) - except: - return "Unknown", "Unknown" - dbmap1 = dbshelve.DBShelf(env) - fname = os.path.join(file_name, META + ".db") - try: - dbmap1.open(fname, META, db.DB_HASH, db.DB_RDONLY) - except: - return "Unknown", "Unknown" - version = dbmap1.get('version', default=None) - dbmap1.close() - dbmap2 = dbshelve.DBShelf(env) - fname = os.path.join(file_name, PERSON_TBL + ".db") - try: - dbmap2.open(fname, PERSON_TBL, db.DB_HASH, db.DB_RDONLY) - except: - env.close() - return "Unknown", "Unknown" - count = len(dbmap2) - dbmap2.close() - env.close() - return (count, version) - - def family_tree_summary(self): - """ - Return a list of dictionaries of the known family trees. - """ - # make the default directory if it does not exist - list = [] - for item in self.current_names: - (name, dirpath, path_name, last, - tval, enable, stock_id) = item - count, version = self.get_dbdir_summary(dirpath) - retval = {} - retval["Number of people"] = count - if enable: - retval["Locked?"] = "yes" - else: - retval["Locked?"] = "no" - retval["DB version"] = version - retval["Family tree"] = name - retval["Path"] = dirpath - retval["Last accessed"] = time.strftime('%x %X', - time.localtime(tval)) - list.append( retval ) - return list - - def _populate_cli(self): - """ Get the list of current names in the database dir - """ - # make the default directory if it does not exist - dbdir = os.path.expanduser(Config.get(Config.DATABASE_PATH)) - make_dbdir(dbdir) - - self.current_names = [] - - for dpath in os.listdir(dbdir): - dirpath = os.path.join(dbdir, dpath) - path_name = os.path.join(dirpath, NAME_FILE) - if os.path.isfile(path_name): - name = file(path_name).readline().strip() - - (tval, last) = time_val(dirpath) - (enable, stock_id) = icon_values(dirpath, self.active, - self.dbstate.db.is_open()) - - if (stock_id == 'gramps-lock'): - last = find_locker_name(dirpath) - - self.current_names.append( - (name, os.path.join(dbdir, dpath), path_name, - last, tval, enable, stock_id)) - - self.current_names.sort() - - def get_family_tree_path(self, name): - """ - Given a name, return None if name not existing or the path to the - database if it is a known database name. - """ - for data in self.current_names: - if data[0] == name: - return data[1] - return None - - def family_tree_list(self): - """Return a list of name, dirname of the known family trees - """ - lst = [(x[0], x[1]) for x in self.current_names] - return lst - - def __start_cursor(self, msg): - """ - Do needed things to start import visually, eg busy cursor - """ - print _('Starting Import, %s') % msg - - def __end_cursor(self): - """ - Set end of a busy cursor - """ - print _('Import finished...') - - def _create_new_db_cli(self, title=None): - """ - Create a new database. - """ - new_path = find_next_db_dir() - - os.mkdir(new_path) - path_name = os.path.join(new_path, NAME_FILE) - - if title is None: - name_list = [ name[0] for name in self.current_names ] - title = find_next_db_name(name_list) - - name_file = open(path_name, "w") - name_file.write(title) - name_file.close() - - # write the version number into metadata - newdb = gen.db.GrampsDBDir() - newdb.write_version(new_path) - - (tval, last) = time_val(new_path) - - self.current_names.append((title, new_path, path_name, - last, tval, False, "")) - return new_path, title - - def _create_new_db(self, title=None): - """ - Create a new database, do extra stuff needed - """ - return self._create_new_db_cli(title) - - def import_new_db(self, filename, callback): - """ - Attempt to import the provided file into a new database. - A new database will only be created if an appropriate importer was - found. - - @return: A tuple of (new_path, name) for the new database - or (None, None) if no import was performed. - """ - pmgr = PluginManager.get_instance() - (name, ext) = os.path.splitext(os.path.basename(filename)) - format = ext[1:].lower() - - for plugin in pmgr.get_import_plugins(): - if format == plugin.get_extension(): - - new_path, name = self._create_new_db(name) - - # Create a new database - self.__start_cursor(_("Importing data...")) - dbclass = gen.db.GrampsDBDir - dbase = dbclass() - dbase.load(new_path, callback) - - import_function = plugin.get_import_function() - import_function(dbase, filename, callback) - - # finish up - self.__end_cursor() - dbase.close() - - return new_path, name - return None, None - - def is_locked(self, dbpath): - """ - returns True if there is a lock file in the dirpath - """ - if os.path.isfile(os.path.join(dbpath,"lock")): - return True - return False - - def needs_recovery(self, dbpath): - """ - returns True if the database in dirpath needs recovery - """ - if os.path.isfile(os.path.join(dbpath,"need_recover")): - return True - return False - - def break_lock(self, dbpath): - """ - Breaks the lock on a database - """ - if os.path.exists(os.path.join(dbpath, "lock")): - os.unlink(os.path.join(dbpath, "lock")) - class DbManager(CLIDbManager): """ Database Manager. Opens a database manager window that allows users to @@ -952,72 +724,6 @@ def drop_cb(wid, context, xpos, ypos, time_stamp): context.finish(True, False, time_stamp) return True - -def find_next_db_name(name_list): - """ - Scan the name list, looking for names that do not yet exist. - Use the DEFAULT_TITLE as the basis for the database name. - """ - i = 1 - while True: - title = "%s %d" % (DEFAULT_TITLE, i) - if title not in name_list: - return title - i += 1 - -def find_next_db_dir(): - """ - Searches the default directory for the first available default - database name. Base the name off the current time. In all actuality, - the first should be valid. - """ - while True: - base = "%x" % int(time.time()) - dbdir = os.path.expanduser(Config.get(Config.DATABASE_PATH)) - new_path = os.path.join(dbdir, base) - if not os.path.isdir(new_path): - break - return new_path - -def make_dbdir(dbdir): - """ - Create the default database directory, as defined by dbdir - """ - try: - if not os.path.isdir(dbdir): - os.makedirs(dbdir) - except (IOError, OSError), msg: - LOG.error(_("Could not make database directory: ") + str(msg)) - -def time_val(dirpath): - """ - Return the last modified time of the database. We do this by looking - at the modification time of the meta db file. If this file does not - exist, we indicate that database as never modified. - """ - meta = os.path.join(dirpath, META_NAME) - if os.path.isfile(meta): - tval = os.stat(meta)[9] - last = time.strftime('%x %X', time.localtime(tval)) - else: - tval = 0 - last = _("Never") - return (tval, last) - -def icon_values(dirpath, active, is_open): - """ - If the directory path is the active path, then return values - that indicate to use the icon, and which icon to use. - """ - if os.path.isfile(os.path.join(dirpath,"need_recover")): - return (True, gtk.STOCK_DIALOG_ERROR) - elif dirpath == active and is_open: - return (True, gtk.STOCK_OPEN) - elif os.path.isfile(os.path.join(dirpath,"lock")): - return (True, 'gramps-lock') - else: - return (False, "") - def find_revisions(name): """ Finds all the revisions of the specfied RCS archive. @@ -1062,23 +768,7 @@ def find_revisions(name): del proc return revlist -def find_locker_name(dirpath): - """ - Opens the lock file if it exists, reads the contexts which is "USERNAME" - and returns the contents, with correct string before "USERNAME", - so the message can be printed with correct locale. - If a file is encountered with errors, we return 'Unknown' - This data is displayed in the time column of the manager - """ - try: - fname = os.path.join(dirpath, "lock") - ifile = open(fname) - username = ifile.read().strip() - last = _("Locked by %s") % username - ifile.close() - except (OSError, IOError): - last = _("Unknown") - return last + def check_out(dbase, rev, path, callback): """ diff --git a/src/ViewManager.py b/src/gui/viewmanager.py similarity index 89% rename from src/ViewManager.py rename to src/gui/viewmanager.py index aeed0024a..918f0ab97 100644 --- a/src/ViewManager.py +++ b/src/gui/viewmanager.py @@ -55,9 +55,9 @@ import gtk # GRAMPS modules # #------------------------------------------------------------------------- +from cli import CLIManager from PluginUtils import Tool, PluginWindows, \ ReportPluginDialog, ToolPluginDialog -from gen.plug import PluginManager import ReportBase import DisplayState import const @@ -72,7 +72,7 @@ import RecentFiles from BasicUtils import name_displayer import widgets import UndoHistory -from DbLoader import DbLoader +from gui.dbloader import DbLoader import GrampsDisplay from gen.utils import ProgressMonitor from GrampsAboutDialog import GrampsAboutDialog @@ -178,17 +178,20 @@ UIDEFAULT = ''' WIKI_HELP_PAGE_FAQ = '%s_-_FAQ' % const.URL_MANUAL_PAGE WIKI_HELP_PAGE_KEY = '%s_-_Keybindings' % const.URL_MANUAL_PAGE WIKI_HELP_PAGE_MAN = '%s' % const.URL_MANUAL_PAGE + #------------------------------------------------------------------------- # # ViewManager # #------------------------------------------------------------------------- -class ViewManager(object): + +class ViewManager(CLIManager): """ Overview ======== - The ViewManager is the main window of the program. It is closely tied + The ViewManager is the session manager of the program. + Specifically, it manages the main window of the program. It is closely tied into the gtk.UIManager to control all menus and actions. The ViewManager controls the various Views within the GRAMPS programs. @@ -204,14 +207,13 @@ class ViewManager(object): The View Manager does not have to know the number of views, the type of views, or any other details about the views. It simply provides the - method of containing each view, and switching between the views.s + method of containing each view, and switching between the views. """ - def __init__(self, state): - + def __init__(self, dbstate): + CLIManager.__init__(self, dbstate, False) self.page_is_changing = False - self.state = state self.active_page = None self.views = [] self.pages = [] @@ -220,19 +222,24 @@ class ViewManager(object): self.merge_ids = [] self.tips = gtk.Tooltips() self._key = None - self.file_loaded = False self.show_sidebar = Config.get(Config.VIEW) self.show_toolbar = Config.get(Config.TOOLBAR_ON) self.show_filter = Config.get(Config.FILTER) self.fullscreen = Config.get(Config.FULLSCREEN) - - self.__pmgr = PluginManager.get_instance() self.__build_main_window() self.__connect_signals() - self.__do_load_plugins() + self.do_load_plugins() + def _errordialog(title, errormessage): + """ + Show the error. + In the GUI, the error is shown, and a return happens + """ + ErrorDialog(title, errormessage) + return 1 + def __build_main_window(self): """ Builds the GTK interface @@ -244,7 +251,7 @@ class ViewManager(object): self.window.set_icon_from_file(const.ICON) self.window.set_default_size(width, height) - self.rel_class = self.__pmgr.get_relationship_calculator() + self.rel_class = self._pmgr.get_relationship_calculator() vbox = gtk.VBox() self.window.add(vbox) @@ -278,7 +285,7 @@ class ViewManager(object): self.window, self.statusbar, self.progress, self.warnbtn, self.uimanager, self.progress_monitor, self) - self.state.connect('database-changed', self.uistate.db_changed) + self.dbstate.connect('database-changed', self.uistate.db_changed) self.filter_menu = self.uimanager.get_widget( '/MenuBar/ViewMenu/Filter/') @@ -290,14 +297,14 @@ class ViewManager(object): self.uistate.set_open_widget(openbtn) self.toolbar.insert(openbtn, 0) - self.person_nav = Navigation.PersonNavigation(self.state, self.uistate) + self.person_nav = Navigation.PersonNavigation(self.dbstate, self.uistate) self._navigation_type[PageView.NAVIGATION_PERSON] = (self.person_nav, None) self.recent_manager = DisplayState.RecentDocsMenu( - self.uistate, self.state, self.__read_recent_file) + self.uistate, self.dbstate, self._read_recent_file) self.recent_manager.build() - self.db_loader = DbLoader(self.state, self.uistate) + self.db_loader = DbLoader(self.dbstate, self.uistate) self.__setup_sidebar() @@ -501,7 +508,7 @@ class ViewManager(object): try: self.active_page.call_function(name) except Exception: - self.uistate.push_message(self.state, + self.uistate.push_message(self.dbstate, _("Key %s is not bound") % name) def __next_view(self, action): @@ -549,10 +556,10 @@ class ViewManager(object): self.actiongroup.set_visible(False) self.readonlygroup.set_visible(False) self.fileactions.set_sensitive(False) - self.__build_tools_menu(self.__pmgr.get_tool_list()) - self.__build_report_menu(self.__pmgr.get_report_list()) + self.__build_tools_menu(self._pmgr.get_tool_list()) + self.__build_report_menu(self._pmgr.get_report_list()) self.uistate.set_relationship_class() - self.__pmgr.connect('plugins-reloaded', + self._pmgr.connect('plugins-reloaded', self.__rebuild_report_and_tool_menus) self.fileactions.set_sensitive(True) self.uistate.widget.set_sensitive(True) @@ -565,7 +572,7 @@ class ViewManager(object): """ Callback function for statusbar key update """ - self.uistate.modify_statusbar(self.state) + self.uistate.modify_statusbar(self.dbstate) def __filter_signal(self, client, cnxn_id, entry, data): """ @@ -580,7 +587,7 @@ class ViewManager(object): ArgHandler can work without it always shown """ self.window.show() - if not self.state.db.is_open() and show_manager: + if not self.dbstate.db.is_open() and show_manager: self.__open_activate(None) def post_load_newdb(self, filename, filetype): @@ -592,23 +599,22 @@ class ViewManager(object): ifile.close() except: title = filename - self.__post_load_newdb(filename, filetype, title) + self._post_load_newdb(filename, filetype, title) - def __do_load_plugins(self): + def do_load_plugins(self): """ Loads the plugins at initialization time. The plugin status window is opened on an error if the user has requested. """ # load plugins self.uistate.status_text(_('Loading plugins...')) - error = self.__pmgr.load_plugins(const.PLUGINS_DIR) - error |= self.__pmgr.load_plugins(const.USER_PLUGINS) + error = CLIManager.do_load_plugins(self) # get to see if we need to open the plugin status window if error and Config.get(Config.POP_PLUGIN_STATUS): self.__plugin_status() - self.uistate.push_message(self.state, _('Ready')) + self.uistate.push_message(self.dbstate, _('Ready')) def quit(self, *obj): """ @@ -619,7 +625,7 @@ class ViewManager(object): # backup data, and close the database self.__backup() - self.state.db.close() + self.dbstate.db.close() # have each page save anything, if they need to: self.__delete_pages() @@ -637,11 +643,11 @@ class ViewManager(object): """ import GrampsDbUtils - if self.state.db.undoindex >= 0: + if self.dbstate.db.undoindex >= 0: self.uistate.set_busy_cursor(1) self.uistate.progress.show() - self.uistate.push_message(self.state, _("Autobackup...")) - GrampsDbUtils.Backup.backup(self.state.db) + self.uistate.push_message(self.dbstate, _("Autobackup...")) + GrampsDbUtils.Backup.backup(self.dbstate.db) self.uistate.set_busy_cursor(0) self.uistate.progress.hide() @@ -649,7 +655,7 @@ class ViewManager(object): """ Abandon changes and quit. """ - if self.state.db.abort_possible: + if self.dbstate.db.abort_possible: dialog = QuestionDialog2( _("Abort changes?"), @@ -659,8 +665,8 @@ class ViewManager(object): _("Cancel")) if dialog.run(): - self.state.db.disable_signals() - while self.state.db.undo(): + self.dbstate.db.disable_signals() + while self.dbstate.db.undo(): pass self.quit() else: @@ -713,7 +719,7 @@ class ViewManager(object): Open the preferences dialog. """ try: - GrampsCfg.GrampsPreferences(self.uistate, self.state) + GrampsCfg.GrampsPreferences(self.uistate, self.dbstate) self._key = self.uistate.connect('nameformat-changed', self.active_page.build_tree) except Errors.WindowActiveError: @@ -824,7 +830,7 @@ class ViewManager(object): index = 0 for page_def in self.views: - page = page_def(self.state, self.uistate) + page = page_def(self.dbstate, self.uistate) page_title = page.get_title() page_stock = page.get_stock() @@ -1013,7 +1019,7 @@ class ViewManager(object): # set button of current page active self.__set_active_button(num) - if self.state.open: + if self.dbstate.open: self.__disconnect_previous_page() @@ -1041,89 +1047,60 @@ class ViewManager(object): """ Imports a file """ - if self.state.db.is_open(): + if self.dbstate.db.is_open(): self.db_loader.import_file() infotxt = self.db_loader.import_info_text() if infotxt: InfoDialog(_('Import Statistics'), infotxt, self.window) self.__post_load() - def open_activate(self, path): - """ - Open and make a family tree active - """ - self.__read_recent_file(path) def __open_activate(self, obj): """ Called when the Open button is clicked, opens the DbManager """ - import DbManager - dialog = DbManager.DbManager(self.state, self.window) + from dbman import DbManager + dialog = DbManager(self.dbstate, self.window) value = dialog.run() if value: (filename, title) = value self.db_loader.read_file(filename) - self.__post_load_newdb(filename, 'x-directory/normal', title) - - def __read_recent_file(self, filename): - """ - Called when the recent file is loaded - """ - # A recent database should already have a directory - # If not, do nothing, just return. This can be handled better if family tree - # delete/rename also updated the recent file menu info in DisplayState.py - if not os.path.isdir(filename): - ErrorDialog( - _("Could not load a recent Family Tree."), - _("Family Tree does not exist, as it has been deleted.")) - return - - if self.db_loader.read_file(filename): - # Attempt to figure out the database title - path = os.path.join(filename, "name.txt") - try: - ifile = open(path) - title = ifile.readline().strip() - ifile.close() - except: - title = filename - - self.__post_load_newdb(filename, 'x-directory/normal', title) + self._post_load_newdb(filename, 'x-directory/normal', title) def __post_load(self): """ This method is for the common UI post_load, both new files and added data like imports. """ - if self.state.active : + if self.dbstate.active : # clear history and fill history with first entry, active person - self.uistate.clear_history(self.state.active.handle) + self.uistate.clear_history(self.dbstate.active.handle) else : self.uistate.clear_history(None) self.uistate.progress.hide() - self.state.db.undo_callback = self.__change_undo_label - self.state.db.redo_callback = self.__change_redo_label + self.dbstate.db.undo_callback = self.__change_undo_label + self.dbstate.db.redo_callback = self.__change_redo_label self.__change_undo_label(None) self.__change_redo_label(None) - self.state.db.undo_history_callback = self.undo_history_update + self.dbstate.db.undo_history_callback = self.undo_history_update self.undo_history_close() self.uistate.window.window.set_cursor(None) - def __post_load_newdb(self, filename, filetype, title=None): + def _post_load_newdb(self, filename, filetype, title=None): """ - Called after a new database is loaded. + The method called after load of a new database. + Inherit CLI method to add GUI part """ - if not filename: - return - - # This method is for UI stuff when the database has changed. - # Window title, recent files, etc related to new file. - - self.state.db.set_save_path(filename) - + self._post_load_newdb_nongui(filename, filetype, title) + self._post_load_newdb_gui(filename, filetype, title) + + def _post_load_newdb_gui(self, filename, filetype, title=None): + """ + Called after a new database is loaded to do GUI stuff + """ + # GUI related post load db stuff # Update window title if filename[-1] == os.path.sep: filename = filename[:-1] @@ -1131,7 +1108,7 @@ class ViewManager(object): if title: name = title - if self.state.db.readonly: + if self.dbstate.db.readonly: msg = "%s (%s) - GRAMPS" % (name, _('Read Only')) self.uistate.window.set_title(msg) self.actiongroup.set_sensitive(False) @@ -1140,39 +1117,15 @@ class ViewManager(object): self.uistate.window.set_title(msg) self.actiongroup.set_sensitive(True) - # apply preferred researcher if loaded file has none - res = self.state.db.get_researcher() - owner = GrampsCfg.get_researcher() - if res.get_name() == "" and owner.get_name() != "": - self.state.db.set_researcher(owner) - self.setup_bookmarks() - name_displayer.set_name_format(self.state.db.name_formats) - fmt_default = Config.get(Config.NAME_FORMAT) - if fmt_default < 0: - name_displayer.set_default_format(fmt_default) - - self.state.db.enable_signals() - self.state.signal_change() - - Config.set(Config.RECENT_FILE, filename) - - try: - self.state.change_active_person(self.state.db.find_initial_person()) - except: - pass - self.change_page(None, None) self.actiongroup.set_visible(True) self.readonlygroup.set_visible(True) - - self.file_loaded = True - - RecentFiles.recent_files(filename, name) + self.recent_manager.build() - # Call common __post_load + # Call common __post_load method for GUI update after a change self.__post_load() def __change_undo_label(self, label): @@ -1240,16 +1193,16 @@ class ViewManager(object): """ import Bookmarks self.bookmarks = Bookmarks.Bookmarks( - self.state, self.uistate, self.state.db.get_bookmarks()) + self.dbstate, self.uistate, self.dbstate.db.get_bookmarks()) def add_bookmark(self, obj): """ Add a bookmark to the bookmark list """ - if self.state.active: - self.bookmarks.add(self.state.active.get_handle()) - name = name_displayer.display(self.state.active) - self.uistate.push_message(self.state, + if self.dbstate.active: + self.bookmarks.add(self.dbstate.active.get_handle()) + name = name_displayer.display(self.dbstate.active) + self.uistate.push_message(self.dbstate, _("%s has been bookmarked") % name) else: WarningDialog( @@ -1268,7 +1221,7 @@ class ViewManager(object): Displays the Reports dialog """ try: - ReportPluginDialog(self.state, self.uistate, []) + ReportPluginDialog(self.dbstate, self.uistate, []) except Errors.WindowActiveError: return @@ -1277,7 +1230,7 @@ class ViewManager(object): Displays the Tools dialog """ try: - ToolPluginDialog(self.state, self.uistate, []) + ToolPluginDialog(self.dbstate, self.uistate, []) except Errors.WindowActiveError: return @@ -1287,7 +1240,7 @@ class ViewManager(object): """ import ScratchPad try: - ScratchPad.ScratchPadWindow(self.state, self.uistate) + ScratchPad.ScratchPadWindow(self.dbstate, self.uistate) except Errors.WindowActiveError: return @@ -1296,7 +1249,7 @@ class ViewManager(object): Calls the undo function on the database """ self.uistate.set_busy_cursor(1) - self.state.db.undo() + self.dbstate.db.undo() self.uistate.set_busy_cursor(0) def redo(self, obj): @@ -1304,7 +1257,7 @@ class ViewManager(object): Calls the redo function on the database """ self.uistate.set_busy_cursor(1) - self.state.db.redo() + self.dbstate.db.redo() self.uistate.set_busy_cursor(0) def undo_history(self, obj): @@ -1312,7 +1265,7 @@ class ViewManager(object): Displays the Undo history window """ try: - self.undo_history_window = UndoHistory.UndoHistory(self.state, + self.undo_history_window = UndoHistory.UndoHistory(self.dbstate, self.uistate) except Errors.WindowActiveError: return @@ -1321,10 +1274,10 @@ class ViewManager(object): """ Calls the ExportAssistant to export data """ - if self.state.db.db_is_open: + if self.dbstate.db.db_is_open: import ExportAssistant try: - ExportAssistant.ExportAssistant(self.state, self.uistate) + ExportAssistant.ExportAssistant(self.dbstate, self.uistate) except Errors.WindowActiveError: return @@ -1332,8 +1285,8 @@ class ViewManager(object): """ Callback that rebuilds the tools and reports menu """ - tool_menu_list = self.__pmgr.get_tool_list() - report_menu_list = self.__pmgr.get_report_list() + tool_menu_list = self._pmgr.get_tool_list() + report_menu_list = self._pmgr.get_report_list() self.__build_tools_menu(tool_menu_list) self.__build_report_menu(report_menu_list) self.uistate.set_relationship_class() @@ -1403,7 +1356,7 @@ class ViewManager(object): menu_name = ("%s...") % name[2] ofile.write('' % new_key) actions.append((new_key, None, menu_name, None, None, - func(name, self.state, self.uistate))) + func(name, self.dbstate, self.uistate))) ofile.write('') # If there are any unsupported items we add separator @@ -1418,7 +1371,7 @@ class ViewManager(object): new_key = name[3].replace(' ', '-') ofile.write('' % new_key) actions.append((new_key, None, name[2], None, None, - func(name, self.state, self.uistate))) + func(name, self.dbstate, self.uistate))) ofile.write('') ofile.write('')