Split CLI from GUI. These changes allow CLI to work without GTK

Part 1. To do: pylint on new files.


svn: r12674
This commit is contained in:
Benny Malengier
2009-06-18 21:56:37 +00:00
parent c71367ed54
commit 4b7692708c
23 changed files with 1304 additions and 1189 deletions

View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -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__ = {

View File

@ -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'),
@ -53,12 +56,22 @@ class ErrorReportAssistant(object):
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()))

View File

@ -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"""

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.
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):
"""
@ -319,37 +222,59 @@ class ArgHandler(object):
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:
@ -368,103 +293,69 @@ class ArgHandler(object):
print " %s: %s" % (item, summary[item])
sys.exit(0)
if self.help:
print _help
sys.exit(0)
self.__open_action()
self.__import_action()
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)
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)
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)
for expt in self.exports:
print "Exporting: file %s, format %s." % expt
self.cl_export(expt[0], expt[1])
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)
filename = self.open
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)

256
src/cli/argparser.py Normal file
View File

@ -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)

380
src/cli/clidbman.py Normal file
View File

@ -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

284
src/cli/grampscli.py Normal file
View File

@ -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)

View File

@ -23,7 +23,7 @@ dist_pkgdata_DATA = \
dateedit.glade \
editsource.glade \
styleeditor.glade \
dbmanager.glade \
dbman.glade \
editurl.glade \
editrepository.glade \
editreporef.glade \

View File

@ -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():
error = []
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)
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)
build_user_paths()
except OSError, msg:
error += [(_("Configuration error"), str(msg))]
return False
except:
pass
LOG.error("Error reading configuration.", exc_info=True)
return False
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
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)]
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)
#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 quit_now:
gtk.main_quit()
sys.exit(exit_code)
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()
return False
from cli import startcli
startcli(error, argpars)
gobject.timeout_add(100, run, priority=100)
gtk.main()
run()

View File

@ -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"
"<b>BACKUP</b> 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)

View File

@ -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

View File

@ -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" ]

View File

@ -54,6 +54,7 @@ import gobject
# GRAMPS modules
#
#-------------------------------------------------------------------------
from cli.grampscli import CLIDbLoader
import const
import Config
import gen.db
@ -68,12 +69,33 @@ 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
# so we will lose the undo history. Warn the user.
@ -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()

View File

@ -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):
"""

View File

@ -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 = '''<ui>
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,18 +222,23 @@ 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):
"""
@ -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('<menuitem action="%s"/>' % 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('</menu>')
# If there are any unsupported items we add separator
@ -1418,7 +1371,7 @@ class ViewManager(object):
new_key = name[3].replace(' ', '-')
ofile.write('<menuitem action="%s"/>' % 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('</menu>')
ofile.write('</menu></menubar></ui>')