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" PKG_NAME="gramps"
srcdir=`dirname $0` srcdir=`dirname $0`
test -z "$srcdir" && srcdir=. test -z "$srcdir" && srcdir=.
srcfile=$srcdir/src/gramps_main.py srcfile=$srcdir/src/gramps.py
REQUIRED_AUTOMAKE_VERSION=1.9 REQUIRED_AUTOMAKE_VERSION=1.9
DIE=0 DIE=0

View File

@ -4,7 +4,6 @@
# Python files # Python files
# #
src/ansel_utf8.py src/ansel_utf8.py
src/ArgHandler.py
src/Assistant.py src/Assistant.py
src/AutoComp.py src/AutoComp.py
src/Bookmarks.py src/Bookmarks.py
@ -12,8 +11,6 @@ src/ColumnOrder.py
src/const.py src/const.py
src/DateEdit.py src/DateEdit.py
src/Date.py src/Date.py
src/DbLoader.py
src/DbManager.py
src/DdTargets.py src/DdTargets.py
src/DisplayState.py src/DisplayState.py
src/Errors.py src/Errors.py
@ -22,7 +19,6 @@ src/ExportOptions.py
src/GrampsAboutDialog.py src/GrampsAboutDialog.py
src/GrampsCfg.py src/GrampsCfg.py
src/GrampsDisplay.py src/GrampsDisplay.py
src/gramps_main.py
src/gramps.py src/gramps.py
src/ImgManip.py src/ImgManip.py
src/ListModel.py src/ListModel.py
@ -47,10 +43,13 @@ src/TransUtils.py
src/TreeTips.py src/TreeTips.py
src/Utils.py src/Utils.py
src/UndoHistory.py src/UndoHistory.py
src/ViewManager.py
# cli # cli
src/cli/__init__.py src/cli/__init__.py
src/cli/arghandler.py
src/cli/argparser.py
src/cli/clidbman.py
src/cli/grampscli.py
# gen API # gen API
src/gen/__init__.py src/gen/__init__.py
@ -182,6 +181,10 @@ src/gen/plug/docbackend/docbackend.py
# gui - GUI code # gui - GUI code
src/gui/__init__.py 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 # gui/views - the GUI views package
src/gui/views/__init__.py src/gui/views/__init__.py
@ -823,7 +826,7 @@ src/glade/grampscfg.glade
src/glade/dateedit.glade src/glade/dateedit.glade
src/glade/editsource.glade src/glade/editsource.glade
src/glade/styleeditor.glade src/glade/styleeditor.glade
src/glade/dbmanager.glade src/glade/dbman.glade
src/glade/editurl.glade src/glade/editurl.glade
src/glade/editrepository.glade src/glade/editrepository.glade
src/glade/editreporef.glade src/glade/editreporef.glade

View File

@ -170,6 +170,7 @@ class Assistant(gtk.Object, ManagedWindow.ManagedWindow):
gtk.main_iteration() gtk.main_iteration()
def destroy(self, *obj): def destroy(self, *obj):
self.window.emit('delete-event', None)
self.window.destroy() self.window.destroy()
def do_get_property(self, prop): def do_get_property(self, prop):

View File

@ -28,7 +28,7 @@ import Config
class DbState(Callback): class DbState(Callback):
""" """
Provide a class to encapsulate the state of the database.. Provide a class to encapsulate the state of the database.
""" """
__signals__ = { __signals__ = {

View File

@ -8,7 +8,7 @@ import sys, os,bsddb
class ErrorReportAssistant(object): 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._error_detail = error_detail
self._rotate_handler = rotate_handler self._rotate_handler = rotate_handler
@ -18,6 +18,9 @@ class ErrorReportAssistant(object):
self._final_report_text_buffer = None self._final_report_text_buffer = None
self.w = Assistant.Assistant(None,None,self.complete) 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( self.w.add_text_page(
_('Report a bug'), _('Report a bug'),
@ -52,13 +55,23 @@ class ErrorReportAssistant(object):
self.w.connect('page-changed',self.on_page_changed) self.w.connect('page-changed',self.on_page_changed)
self.w.show() 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): def on_page_changed(self, obj,page,data=None):
if page in self.cb: if page in self.cb:
self.cb[page]() self.cb[page]()
def complete(self): def complete(self):
pass if self.ownthread:
#stop the thread we started
gtk.main_quit()
def _copy_to_clipboard(self, obj=None): def _copy_to_clipboard(self, obj=None):
clipboard = gtk.Clipboard() clipboard = gtk.Clipboard()
@ -75,7 +88,8 @@ class ErrorReportAssistant(object):
def _start_email_client(self, obj=None): def _start_email_client(self, obj=None):
import GrampsDisplay 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_text(
self._final_report_text_buffer.get_start_iter(), self._final_report_text_buffer.get_start_iter(),
self._final_report_text_buffer.get_end_iter())) self._final_report_text_buffer.get_end_iter()))

View File

@ -46,9 +46,12 @@ class ErrorView(object):
if response == gtk.RESPONSE_HELP: if response == gtk.RESPONSE_HELP:
self.help_clicked() self.help_clicked()
elif response == gtk.RESPONSE_YES: elif response == gtk.RESPONSE_YES:
self.top.destroy()
ErrorReportAssistant(error_detail = self._error_detail, ErrorReportAssistant(error_detail = self._error_detail,
rotate_handler = self._rotate_handler) rotate_handler = self._rotate_handler,
self.top.destroy() ownthread=True)
elif response == gtk.RESPONSE_CANCEL:
self.top.destroy()
def help_clicked(self): def help_clicked(self):
"""Display the relevant portion of GRAMPS manual""" """Display the relevant portion of GRAMPS manual"""

View File

@ -21,9 +21,7 @@ class GtkHandler(logging.Handler):
def emit(self, record): def emit(self, record):
""" """
Add the record to the rotating buffer. Add the record to the rotating buffer.
""" """
self._record = record self._record = record
ErrorView(error_detail=self,rotate_handler=self._rotate_handler) ErrorView(error_detail=self,rotate_handler=self._rotate_handler)

View File

@ -3,6 +3,7 @@
SUBDIRS = \ SUBDIRS = \
BasicUtils \ BasicUtils \
cli \
Config \ Config \
data \ data \
DataViews \ DataViews \
@ -18,6 +19,7 @@ SUBDIRS = \
GrampsDbUtils \ GrampsDbUtils \
GrampsLocale \ GrampsLocale \
GrampsLogger \ GrampsLogger \
gui \
images \ images \
Merge \ Merge \
mapstraction \ mapstraction \
@ -33,7 +35,6 @@ gdirdir=$(prefix)/share/gramps
gdir_PYTHON = \ gdir_PYTHON = \
ansel_utf8.py\ ansel_utf8.py\
ArgHandler.py\
Assistant.py\ Assistant.py\
AutoComp.py\ AutoComp.py\
Bookmarks.py\ Bookmarks.py\
@ -41,8 +42,6 @@ gdir_PYTHON = \
const.py\ const.py\
DateEdit.py\ DateEdit.py\
Date.py\ Date.py\
DbLoader.py\
DbManager.py\
DbState.py\ DbState.py\
DdTargets.py\ DdTargets.py\
DisplayState.py\ DisplayState.py\
@ -52,7 +51,6 @@ gdir_PYTHON = \
GrampsAboutDialog.py\ GrampsAboutDialog.py\
GrampsCfg.py\ GrampsCfg.py\
GrampsDisplay.py\ GrampsDisplay.py\
gramps_main.py\
gramps.py\ gramps.py\
ImgManip.py\ ImgManip.py\
LdsUtils.py \ LdsUtils.py \
@ -77,7 +75,6 @@ gdir_PYTHON = \
TransUtils.py\ TransUtils.py\
TreeTips.py\ TreeTips.py\
Utils.py\ Utils.py\
ViewManager.py\
UndoHistory.py\ UndoHistory.py\
PlaceUtils.py\ PlaceUtils.py\
ProgressDialog.py ProgressDialog.py

View File

@ -6,7 +6,11 @@
pkgdatadir = $(datadir)/@PACKAGE@/cli pkgdatadir = $(datadir)/@PACKAGE@/cli
pkgdata_PYTHON = \ pkgdata_PYTHON = \
__init__.py __init__.py \
arghandler.py \
argparser.py \
clidbman.py \
grampscli.py
pkgpyexecdir = @pkgpyexecdir@/cli pkgpyexecdir = @pkgpyexecdir@/cli
pkgpythondir = @pkgpythondir@/cli pkgpythondir = @pkgpythondir@/cli

View File

@ -22,3 +22,8 @@
""" """
Package init for the cli package. 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 # Gramps - a GTK+/GNOME based genealogy program
# #
# Copyright (C) 2000-2006 Donald N. Allingham, A. Roitman # 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 Lukasz Rymarczyk
# Copyright (C) 2008 Raphael Ackermann # Copyright (C) 2008 Raphael Ackermann
# Copyright (C) 2008 Brian G. Matherly # Copyright (C) 2008 Brian G. Matherly
@ -22,7 +22,7 @@
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # 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. Module responsible for handling the command line arguments for GRAMPS.
@ -49,186 +49,83 @@ import Config
import RecentFiles import RecentFiles
import Utils import Utils
import gen 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 PluginUtils import Tool
from gen.plug import PluginManager from gen.plug import PluginManager
from ReportBase import CATEGORY_BOOK, CATEGORY_CODE, cl_report 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 # ArgHandler
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
class ArgHandler(object): class ArgHandler(object):
""" """
This class is responsible for handling command line arguments (if any) This class is responsible for the non GUI handling of commands
given to gramps. The valid arguments are: The handler is passed a parser object, sanitizes it, and can execute the
actions requested working on a DbState
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.
""" """
def __init__(self, state, vm, args): def __init__(self, dbstate, parser, sessionmanager,
self.state = state errorfunc=None, gui=False):
self.vm = vm self.dbstate = dbstate
self.args = args self.sm = sessionmanager
self.errorfunc = errorfunc
self.open_gui = None self.gui = gui
self.open = None self.dbman = CLIDbManager(self.dbstate)
self.force_unlock = parser.force_unlock
self.open = self.__handle_open_option(parser.open)
self.cl = 0 self.cl = 0
self.exports = []
self.actions = []
self.imports = [] 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.imp_db_path = None
self.list = False
self.list_more = False def error(self, string):
self.help = False if self.errorfunc:
self.force_unlock = False self.errorfunc(string)
self.dbman = CLIDbManager(self.state) else:
print string
self.parse_args()
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
# Argument parser: sorts out given arguments # 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. check the 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
""" """
try: for (value, format) in importlist:
options, leftargs = getopt.getopt(self.args[1:], self.__handle_import_option(value, format)
const.SHORTOPTS, const.LONGOPTS) for (value, format) in exportlist:
except getopt.GetoptError, msg: self.__handle_export_option(value, format)
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
def __handle_open_option(self, value): def __handle_open_option(self, value):
""" """
Handle the "-O" or "--open" option. Handle the "-O" or "--open" option.
""" """
if value is None:
return None
db_path = self.__deduce_db_path(value) db_path = self.__deduce_db_path(value)
if db_path: if db_path:
# We have a potential database path. # We have a potential database path.
# Check if it is good. # 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) sys.exit(0)
self.open = db_path return db_path
else: else:
print _('Input family tree "%s" does not exist.') % value self.error( _('Error: Input family tree "%s" does not exist.\n'
print _("If gedcom, gramps-xml or grdb, use the -i option to " "If gedcom, gramps-xml or grdb, use the -i option to "
"import into a family tree instead") "import into a family tree instead.") % value)
sys.exit(0) sys.exit(0)
def __handle_import_option(self, value, format): def __handle_import_option(self, value, format):
@ -238,9 +135,8 @@ class ArgHandler(object):
fname = value fname = value
fullpath = os.path.abspath(os.path.expanduser(fname)) fullpath = os.path.abspath(os.path.expanduser(fname))
if not os.path.exists(fullpath): if not os.path.exists(fullpath):
print 'Import file not found.' self.error(_('Error: Import file %s not found.') % fname)
print "Ignoring import file: %s" % fname sys.exit(0)
return
if format is None: if format is None:
# Guess the file format based on the file extension. # Guess the file format based on the file extension.
@ -257,27 +153,33 @@ class ArgHandler(object):
if plugin_found: if plugin_found:
self.imports.append((fname, format)) self.imports.append((fname, format))
else: else:
print 'Unrecognized type: "%s" for import file: %s' \ self.error(_('Error: Unrecognized type: "%(format)s" for '
% (format, fname) 'import file: %(filename)s') \
print "Ignoring import file: %s" % fname % {'format' : format,
'filename' : fname})
sys.exit(0)
def __handle_export_option(self, value, format): def __handle_export_option(self, value, format):
""" """
Handle the "-e" or "--export" option. Handle the "-e" or "--export" option.
Note: only the CLI version has export
""" """
if self.gui:
return
fname = value fname = value
fullpath = os.path.abspath(os.path.expanduser(fname)) fullpath = os.path.abspath(os.path.expanduser(fname))
if os.path.exists(fullpath): if os.path.exists(fullpath):
print "WARNING: Output file already exist!" self.error(_("WARNING: Output file already exist!\n"
print "WARNING: It will be overwritten:\n %s" % fullpath "WARNING: It will be overwritten:\n %(name)s") % \
{'name' : fullpath})
answer = None answer = None
while not answer: while not answer:
answer = raw_input('OK to overwrite? (yes/no) ') answer = raw_input(_('OK to overwrite? (yes/no) '))
if answer.upper() in ('Y','YES'): if answer.upper() in ('Y','YES', _('YES')):
print "Will overwrite the existing file: %s" % fullpath self.error( _("Will overwrite the existing file: %s")
% fullpath)
else: else:
print "Will skip the output file: %s" % fullpath sys.exit(0)
return
if format is None: if format is None:
# Guess the file format based on the file extension. # Guess the file format based on the file extension.
@ -294,8 +196,9 @@ class ArgHandler(object):
if plugin_found: if plugin_found:
self.exports.append((fullpath, format)) self.exports.append((fullpath, format))
else: else:
print "Unrecognized format for export file %s" % fname self.error(_("ERROR: Unrecognized format for export file %s")
print "Ignoring export file: %s" % fname % fname)
sys.exit(0)
def __deduce_db_path(self, db_name_or_path): def __deduce_db_path(self, db_name_or_path):
""" """
@ -318,38 +221,60 @@ class ArgHandler(object):
db_path = fullpath db_path = fullpath
return db_path 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: # Overall argument handler:
# sorts out the sequence and details of operations # 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 Depending on the given arguments, import or open data, launch
session, write files, and/or perform actions. session, write files, and/or perform actions.
@param: climan: the manager of a CLI session
@type: CLIManager object
""" """
if self.list: if self.list:
@ -367,104 +292,70 @@ class ArgHandler(object):
if item != "Family tree": if item != "Family tree":
print " %s: %s" % (item, summary[item]) print " %s: %s" % (item, summary[item])
sys.exit(0) sys.exit(0)
if self.help:
print _help
sys.exit(0)
if self.open_gui:
# First check if a Gramps database was provided
# (either a database path or a database name)
db_path = self.__deduce_db_path(self.open_gui)
if not db_path:
# Apparently it is not a database. See if it is a file that
# can be imported.
db_path, title = self.dbman.import_new_db(self.open_gui, None)
if db_path:
# Test if not locked or problematic
if not self.__check_db(db_path, self.force_unlock):
sys.exit(0)
# Add the file to the recent items
path = os.path.join(db_path, "name.txt")
try:
ifile = open(path)
title = ifile.readline().strip()
ifile.close()
except:
title = db_path
RecentFiles.recent_files(db_path, title)
else:
sys.exit(1)
return db_path
if self.open: self.__open_action()
# Family Tree to open was given. Open it self.__import_action()
# Then go on and process the rest of the command line arguments.
self.cl = bool(self.exports or self.actions) for (action, options_str) in self.actions:
print "Performing action: %s." % action
if options_str:
print "Using options string: %s" % options_str
self.cl_action(action, options_str)
filename = self.open for expt in self.exports:
print "Exporting: file %s, format %s." % expt
self.cl_export(expt[0], expt[1])
try: print "Cleaning up."
self.vm.open_activate(filename) # remove files in import db subdir after use
print "Opened successfully!" self.dbstate.db.close()
except: if self.imp_db_path:
print "Error opening the file." Utils.rm_tempdir(self.imp_db_path)
print "Exiting..." print "Exiting."
sys.exit(1) sys.exit(0)
def __import_action(self):
if self.imports: if self.imports:
self.cl = bool(self.exports or self.actions or self.cl) self.cl = bool(self.exports or self.actions or self.cl)
if not self.open: if not self.open:
# Create empty dir for imported database(s) # 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 = gen.db.GrampsDBDir()
newdb.write_version(self.imp_db_path) 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) sys.exit(0)
for imp in self.imports: for imp in self.imports:
print "Importing: file %s, format %s." % imp print "Importing: file %s, format %s." % imp
self.cl_import(imp[0], imp[1]) self.cl_import(imp[0], imp[1])
elif len(self.args) > 1 and not self.open: def __open_action(self):
print "No data was given -- will launch interactive session." if self.open:
print "To use in the command-line mode,", \ # Family Tree to open was given. Open it
"supply at least one input file to process." # Then go on and process the rest of the command line arguments.
print "Launching interactive session..." self.cl = bool(self.exports or self.actions)
if self.cl: # we load this file for use
for (action, options_str) in self.actions: try:
print "Performing action: %s." % action self.sm.open_activate(self.open)
if options_str: print "Opened successfully!"
print "Using options string: %s" % options_str except:
self.cl_action(action, options_str) print "Error opening the file."
print "Exiting..."
sys.exit(0)
for expt in self.exports: def check_db(self, dbpath, force_unlock = False):
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):
# Test if not locked or problematic # Test if not locked or problematic
if force_unlock: if force_unlock:
self.dbman.break_lock(dbpath) self.dbman.break_lock(dbpath)
@ -485,19 +376,18 @@ class ArgHandler(object):
def cl_import(self, filename, format): def cl_import(self, filename, format):
""" """
Command-line import routine. Try to import filename using the 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() pmgr = PluginManager.get_instance()
for plugin in pmgr.get_import_plugins(): for plugin in pmgr.get_import_plugins():
if format == plugin.get_extension(): if format == plugin.get_extension():
import_function = plugin.get_import_function() 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 not self.cl:
if self.imp_db_path: if self.imp_db_path:
return self.vm.open_activate(self.imp_db_path) return self.sm.open_activate(self.imp_db_path)
else: 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. Command-line export routine.
Try to write into filename using the format. Try to write into filename using the format.
Any errors will cause the sys.exit(1) call.
""" """
pmgr = PluginManager.get_instance() pmgr = PluginManager.get_instance()
for plugin in pmgr.get_export_plugins(): for plugin in pmgr.get_export_plugins():
if format == plugin.get_extension(): if format == plugin.get_extension():
export_function = plugin.get_export_function() 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() pmgr = PluginManager.get_instance()
if action == 'check': if action == 'check':
import 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.check_for_broken_family_links()
checker.cleanup_missing_photos(1) checker.cleanup_missing_photos(1)
checker.check_parent_relationships() checker.check_parent_relationships()
@ -538,7 +427,7 @@ class ArgHandler(object):
checker.report(1) checker.report(1)
elif action == 'summary': elif action == 'summary':
import Summary import Summary
text = Summary.build_report(self.state.db, None) text = Summary.build_report(self.dbstate.db, None)
print text print text
elif action == "report": elif action == "report":
try: try:
@ -557,10 +446,10 @@ class ArgHandler(object):
report_class = item[2] report_class = item[2]
options_class = item[3] options_class = item[3]
if category in (CATEGORY_BOOK, CATEGORY_CODE): if category in (CATEGORY_BOOK, CATEGORY_CODE):
options_class(self.state.db, name, category, options_class(self.dbstate.db, name, category,
options_str_dict) options_str_dict)
else: else:
cl_report(self.state.db, name, category, report_class, cl_report(self.dbstate.db, name, category, report_class,
options_class, options_str_dict) options_class, options_str_dict)
return return
# name exists, but is not in the list of valid report names # name exists, but is not in the list of valid report names
@ -593,7 +482,7 @@ class ArgHandler(object):
category = item[1] category = item[1]
tool_class = item[2] tool_class = item[2]
options_class = item[3] 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) options_class, options_str_dict)
return return
msg = "Unknown tool name." msg = "Unknown tool name."
@ -605,5 +494,4 @@ class ArgHandler(object):
print " %s" % item[0] print " %s" % item[0]
else: else:
print "Unknown action: %s." % action 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 \ dateedit.glade \
editsource.glade \ editsource.glade \
styleeditor.glade \ styleeditor.glade \
dbmanager.glade \ dbman.glade \
editurl.glade \ editurl.glade \
editrepository.glade \ editrepository.glade \
editreporef.glade \ editreporef.glade \

View File

@ -2,6 +2,7 @@
# Gramps - a GTK+/GNOME based genealogy program # Gramps - a GTK+/GNOME based genealogy program
# #
# Copyright (C) 2000-2006 Donald N. Allingham # Copyright (C) 2000-2006 Donald N. Allingham
# Copyright (C) 2009 Benny Malengier
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by
@ -33,29 +34,14 @@ import signal
import gettext import gettext
import logging import logging
log = logging.getLogger(".") LOG = logging.getLogger(".")
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
# #
# pygtk # GRAMPS modules
# #
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
try: from Mime import mime_type_is_defined
import pygtk
pygtk.require('2.0')
except ImportError:
pass
#-------------------------------------------------------------------------
#
# Miscellaneous initialization
#
#-------------------------------------------------------------------------
import gtk
from gtk import glade
import gobject
gobject.threads_init()
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
# #
@ -78,13 +64,6 @@ except ValueError:
pass pass
gettext.bindtextdomain("gramps",loc) gettext.bindtextdomain("gramps",loc)
glade.bindtextdomain("gramps",loc)
try:
glade.textdomain("gramps")
except:
pass
gettext.textdomain("gramps") gettext.textdomain("gramps")
gettext.install("gramps",loc,unicode=1) gettext.install("gramps",loc,unicode=1)
@ -117,24 +96,15 @@ except:
args = sys.argv args = sys.argv
MIN_PYTHON_VERSION = (2, 5, 0, '', 0)
def setup_logging(): def setup_logging():
"""Setup basic logging support.""" """Setup basic logging support."""
from GrampsLogger import RotateHandler, GtkHandler
# Setup a formatter # Setup a formatter
form = logging.Formatter(fmt="%(relativeCreated)d: %(levelname)s: %(filename)s: line %(lineno)d: %(message)s") form = logging.Formatter(fmt="%(relativeCreated)d: %(levelname)s: %(filename)s: line %(lineno)d: %(message)s")
# Create the log handlers # 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 = logging.StreamHandler(sys.stderr)
stderrh.setFormatter(form) stderrh.setFormatter(form)
stderrh.setLevel(logging.DEBUG) stderrh.setLevel(logging.DEBUG)
@ -143,8 +113,6 @@ def setup_logging():
# everything. # everything.
l = logging.getLogger() l = logging.getLogger()
l.setLevel(logging.WARNING) l.setLevel(logging.WARNING)
l.addHandler(rh)
l.addHandler(gtkh)
l.addHandler(stderrh) l.addHandler(stderrh)
# put a hook on to catch any completely unhandled exceptions. # put a hook on to catch any completely unhandled exceptions.
@ -156,69 +124,53 @@ def setup_logging():
# strange Windows logging error on close # strange Windows logging error on close
return return
import traceback import traceback
log.error("Unhandled exception\n" + LOG.error("Unhandled exception\n" +
"".join(traceback.format_exception(type, value, tb))) "".join(traceback.format_exception(type, value, tb)))
sys.excepthook = exc_hook 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(): 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', setup_logging()
'%s/lib' % const.PREFIXDIR)
program.set_property('app-datadir', try:
'%s/share' % const.PREFIXDIR) build_user_paths()
program.set_property('app-sysconfdir',const.SYSCONFDIR) except OSError, msg:
program.set_property('app-prefix', const.PREFIXDIR) error += [(_("Configuration error"), str(msg))]
return False
except: 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
except SystemExit, e:
quit_now = True
if e.code:
exit_code = e.code
log.error("Gramps terminated with exit code: %d." \
% e.code, exc_info=True)
except OSError, e:
quit_now = True
exit_code = e[0] or 1
try:
fn = e.filename
except AttributeError:
fn = ""
log.error("Gramps terminated because of OS Error\n" +
"Error details: %s %s" % (repr(e), fn), exc_info=True)
except:
quit_now = True
exit_code = 1
log.error("Gramps failed to start.", exc_info=True)
if quit_now:
gtk.main_quit()
sys.exit(exit_code)
return False if not mime_type_is_defined(const.APP_GRAMPS):
error += [(_("Configuration error"),
_("A definition for the MIME-type %s could not "
"be found \n\n Possibly the installation of GRAMPS "
"was incomplete. Make sure the MIME-types "
"of GRAMPS are properly installed.")
% const.APP_GRAMPS)]
#we start with parsing the arguments to determine if we have a cli or a
# gui session
from cli import ArgParser
argpars = ArgParser(sys.argv)
if argpars.need_gui():
#A GUI is needed, set it up
from gui import startgtkloop
startgtkloop(error, argpars)
else:
#CLI use of GRAMPS
argpars.print_help()
gobject.timeout_add(100, run, priority=100) from cli import startcli
gtk.main() startcli(error, argpars)
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 pkgdatadir = $(datadir)/@PACKAGE@/gui
pkgdata_PYTHON = \ pkgdata_PYTHON = \
__init__.py __init__.py \
dbloader.py \
dbman.py \
grampsgui.py \
viewmanager.py
pkgpyexecdir = @pkgpyexecdir@/gui pkgpyexecdir = @pkgpyexecdir@/gui
pkgpythondir = @pkgpythondir@/gui pkgpythondir = @pkgpythondir@/gui

View File

@ -24,4 +24,9 @@
Package init for the gui package. Package init for the gui package.
""" """
from grampsgui import startgtkloop
from viewmanager import ViewManager
from dbman import DbManager
from dbloader import DbLoader
__all__ = [ "views" ] __all__ = [ "views" ]

View File

@ -54,6 +54,7 @@ import gobject
# GRAMPS modules # GRAMPS modules
# #
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
from cli.grampscli import CLIDbLoader
import const import const
import Config import Config
import gen.db import gen.db
@ -68,11 +69,32 @@ import Errors
# DbLoader class # DbLoader class
# #
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
class DbLoader(object): class DbLoader(CLIDbLoader):
def __init__(self, dbstate, uistate): def __init__(self, dbstate, uistate):
self.dbstate = dbstate CLIDbLoader.__init__(self, dbstate)
self.uistate = uistate self.uistate = uistate
self.import_info = None 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): def import_file(self):
# First thing first: import is a batch transaction # First thing first: import is a batch transaction
@ -198,58 +220,6 @@ class DbLoader(object):
return False 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): def do_import(self, dialog, importer, filename):
self.import_info = None self.import_info = None
dialog.destroy() dialog.destroy()

View File

@ -71,6 +71,7 @@ import const
from QuestionDialog import ErrorDialog, QuestionDialog from QuestionDialog import ErrorDialog, QuestionDialog
import gen.db import gen.db
from gen.plug import PluginManager from gen.plug import PluginManager
from cli.clidbman import *
import GrampsDbUtils import GrampsDbUtils
import Config import Config
import Mime import Mime
@ -87,9 +88,7 @@ _KP_ENTER = gtk.gdk.keyval_from_name("KP_Enter")
# constants # constants
# #
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
DEFAULT_TITLE = _("Family Tree")
NAME_FILE = "name.txt"
META_NAME = "meta_data.db"
ARCHIVE = "rev.gramps" ARCHIVE = "rev.gramps"
ARCHIVE_V = "rev.gramps,v" ARCHIVE_V = "rev.gramps,v"
@ -103,233 +102,6 @@ STOCK_COL = 6
RCS_BUTTON = { True : _('_Extract'), False : _('_Archive') } 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): class DbManager(CLIDbManager):
""" """
Database Manager. Opens a database manager window that allows users to 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) context.finish(True, False, time_stamp)
return True 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): def find_revisions(name):
""" """
Finds all the revisions of the specfied RCS archive. Finds all the revisions of the specfied RCS archive.
@ -1062,23 +768,7 @@ def find_revisions(name):
del proc del proc
return revlist 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): def check_out(dbase, rev, path, callback):
""" """

View File

@ -55,9 +55,9 @@ import gtk
# GRAMPS modules # GRAMPS modules
# #
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
from cli import CLIManager
from PluginUtils import Tool, PluginWindows, \ from PluginUtils import Tool, PluginWindows, \
ReportPluginDialog, ToolPluginDialog ReportPluginDialog, ToolPluginDialog
from gen.plug import PluginManager
import ReportBase import ReportBase
import DisplayState import DisplayState
import const import const
@ -72,7 +72,7 @@ import RecentFiles
from BasicUtils import name_displayer from BasicUtils import name_displayer
import widgets import widgets
import UndoHistory import UndoHistory
from DbLoader import DbLoader from gui.dbloader import DbLoader
import GrampsDisplay import GrampsDisplay
from gen.utils import ProgressMonitor from gen.utils import ProgressMonitor
from GrampsAboutDialog import GrampsAboutDialog from GrampsAboutDialog import GrampsAboutDialog
@ -178,17 +178,20 @@ UIDEFAULT = '''<ui>
WIKI_HELP_PAGE_FAQ = '%s_-_FAQ' % const.URL_MANUAL_PAGE WIKI_HELP_PAGE_FAQ = '%s_-_FAQ' % const.URL_MANUAL_PAGE
WIKI_HELP_PAGE_KEY = '%s_-_Keybindings' % const.URL_MANUAL_PAGE WIKI_HELP_PAGE_KEY = '%s_-_Keybindings' % const.URL_MANUAL_PAGE
WIKI_HELP_PAGE_MAN = '%s' % const.URL_MANUAL_PAGE WIKI_HELP_PAGE_MAN = '%s' % const.URL_MANUAL_PAGE
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
# #
# ViewManager # ViewManager
# #
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
class ViewManager(object):
class ViewManager(CLIManager):
""" """
Overview 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. into the gtk.UIManager to control all menus and actions.
The ViewManager controls the various Views within the GRAMPS programs. 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 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 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.page_is_changing = False
self.state = state
self.active_page = None self.active_page = None
self.views = [] self.views = []
self.pages = [] self.pages = []
@ -220,19 +222,24 @@ class ViewManager(object):
self.merge_ids = [] self.merge_ids = []
self.tips = gtk.Tooltips() self.tips = gtk.Tooltips()
self._key = None self._key = None
self.file_loaded = False
self.show_sidebar = Config.get(Config.VIEW) self.show_sidebar = Config.get(Config.VIEW)
self.show_toolbar = Config.get(Config.TOOLBAR_ON) self.show_toolbar = Config.get(Config.TOOLBAR_ON)
self.show_filter = Config.get(Config.FILTER) self.show_filter = Config.get(Config.FILTER)
self.fullscreen = Config.get(Config.FULLSCREEN) self.fullscreen = Config.get(Config.FULLSCREEN)
self.__pmgr = PluginManager.get_instance()
self.__build_main_window() self.__build_main_window()
self.__connect_signals() 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): def __build_main_window(self):
""" """
Builds the GTK interface Builds the GTK interface
@ -244,7 +251,7 @@ class ViewManager(object):
self.window.set_icon_from_file(const.ICON) self.window.set_icon_from_file(const.ICON)
self.window.set_default_size(width, height) 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() vbox = gtk.VBox()
self.window.add(vbox) self.window.add(vbox)
@ -278,7 +285,7 @@ class ViewManager(object):
self.window, self.statusbar, self.progress, self.warnbtn, self.window, self.statusbar, self.progress, self.warnbtn,
self.uimanager, self.progress_monitor, self) 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( self.filter_menu = self.uimanager.get_widget(
'/MenuBar/ViewMenu/Filter/') '/MenuBar/ViewMenu/Filter/')
@ -290,14 +297,14 @@ class ViewManager(object):
self.uistate.set_open_widget(openbtn) self.uistate.set_open_widget(openbtn)
self.toolbar.insert(openbtn, 0) 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, self._navigation_type[PageView.NAVIGATION_PERSON] = (self.person_nav,
None) None)
self.recent_manager = DisplayState.RecentDocsMenu( 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.recent_manager.build()
self.db_loader = DbLoader(self.state, self.uistate) self.db_loader = DbLoader(self.dbstate, self.uistate)
self.__setup_sidebar() self.__setup_sidebar()
@ -501,7 +508,7 @@ class ViewManager(object):
try: try:
self.active_page.call_function(name) self.active_page.call_function(name)
except Exception: except Exception:
self.uistate.push_message(self.state, self.uistate.push_message(self.dbstate,
_("Key %s is not bound") % name) _("Key %s is not bound") % name)
def __next_view(self, action): def __next_view(self, action):
@ -549,10 +556,10 @@ class ViewManager(object):
self.actiongroup.set_visible(False) self.actiongroup.set_visible(False)
self.readonlygroup.set_visible(False) self.readonlygroup.set_visible(False)
self.fileactions.set_sensitive(False) self.fileactions.set_sensitive(False)
self.__build_tools_menu(self.__pmgr.get_tool_list()) self.__build_tools_menu(self._pmgr.get_tool_list())
self.__build_report_menu(self.__pmgr.get_report_list()) self.__build_report_menu(self._pmgr.get_report_list())
self.uistate.set_relationship_class() self.uistate.set_relationship_class()
self.__pmgr.connect('plugins-reloaded', self._pmgr.connect('plugins-reloaded',
self.__rebuild_report_and_tool_menus) self.__rebuild_report_and_tool_menus)
self.fileactions.set_sensitive(True) self.fileactions.set_sensitive(True)
self.uistate.widget.set_sensitive(True) self.uistate.widget.set_sensitive(True)
@ -565,7 +572,7 @@ class ViewManager(object):
""" """
Callback function for statusbar key update 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): def __filter_signal(self, client, cnxn_id, entry, data):
""" """
@ -580,7 +587,7 @@ class ViewManager(object):
ArgHandler can work without it always shown ArgHandler can work without it always shown
""" """
self.window.show() 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) self.__open_activate(None)
def post_load_newdb(self, filename, filetype): def post_load_newdb(self, filename, filetype):
@ -592,23 +599,22 @@ class ViewManager(object):
ifile.close() ifile.close()
except: except:
title = filename 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 Loads the plugins at initialization time. The plugin status window is
opened on an error if the user has requested. opened on an error if the user has requested.
""" """
# load plugins # load plugins
self.uistate.status_text(_('Loading plugins...')) self.uistate.status_text(_('Loading plugins...'))
error = self.__pmgr.load_plugins(const.PLUGINS_DIR) error = CLIManager.do_load_plugins(self)
error |= self.__pmgr.load_plugins(const.USER_PLUGINS)
# get to see if we need to open the plugin status window # get to see if we need to open the plugin status window
if error and Config.get(Config.POP_PLUGIN_STATUS): if error and Config.get(Config.POP_PLUGIN_STATUS):
self.__plugin_status() self.__plugin_status()
self.uistate.push_message(self.state, _('Ready')) self.uistate.push_message(self.dbstate, _('Ready'))
def quit(self, *obj): def quit(self, *obj):
""" """
@ -619,7 +625,7 @@ class ViewManager(object):
# backup data, and close the database # backup data, and close the database
self.__backup() self.__backup()
self.state.db.close() self.dbstate.db.close()
# have each page save anything, if they need to: # have each page save anything, if they need to:
self.__delete_pages() self.__delete_pages()
@ -637,11 +643,11 @@ class ViewManager(object):
""" """
import GrampsDbUtils import GrampsDbUtils
if self.state.db.undoindex >= 0: if self.dbstate.db.undoindex >= 0:
self.uistate.set_busy_cursor(1) self.uistate.set_busy_cursor(1)
self.uistate.progress.show() self.uistate.progress.show()
self.uistate.push_message(self.state, _("Autobackup...")) self.uistate.push_message(self.dbstate, _("Autobackup..."))
GrampsDbUtils.Backup.backup(self.state.db) GrampsDbUtils.Backup.backup(self.dbstate.db)
self.uistate.set_busy_cursor(0) self.uistate.set_busy_cursor(0)
self.uistate.progress.hide() self.uistate.progress.hide()
@ -649,7 +655,7 @@ class ViewManager(object):
""" """
Abandon changes and quit. Abandon changes and quit.
""" """
if self.state.db.abort_possible: if self.dbstate.db.abort_possible:
dialog = QuestionDialog2( dialog = QuestionDialog2(
_("Abort changes?"), _("Abort changes?"),
@ -659,8 +665,8 @@ class ViewManager(object):
_("Cancel")) _("Cancel"))
if dialog.run(): if dialog.run():
self.state.db.disable_signals() self.dbstate.db.disable_signals()
while self.state.db.undo(): while self.dbstate.db.undo():
pass pass
self.quit() self.quit()
else: else:
@ -713,7 +719,7 @@ class ViewManager(object):
Open the preferences dialog. Open the preferences dialog.
""" """
try: try:
GrampsCfg.GrampsPreferences(self.uistate, self.state) GrampsCfg.GrampsPreferences(self.uistate, self.dbstate)
self._key = self.uistate.connect('nameformat-changed', self._key = self.uistate.connect('nameformat-changed',
self.active_page.build_tree) self.active_page.build_tree)
except Errors.WindowActiveError: except Errors.WindowActiveError:
@ -824,7 +830,7 @@ class ViewManager(object):
index = 0 index = 0
for page_def in self.views: 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_title = page.get_title()
page_stock = page.get_stock() page_stock = page.get_stock()
@ -1013,7 +1019,7 @@ class ViewManager(object):
# set button of current page active # set button of current page active
self.__set_active_button(num) self.__set_active_button(num)
if self.state.open: if self.dbstate.open:
self.__disconnect_previous_page() self.__disconnect_previous_page()
@ -1041,89 +1047,60 @@ class ViewManager(object):
""" """
Imports a file Imports a file
""" """
if self.state.db.is_open(): if self.dbstate.db.is_open():
self.db_loader.import_file() self.db_loader.import_file()
infotxt = self.db_loader.import_info_text() infotxt = self.db_loader.import_info_text()
if infotxt: if infotxt:
InfoDialog(_('Import Statistics'), infotxt, self.window) InfoDialog(_('Import Statistics'), infotxt, self.window)
self.__post_load() 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): def __open_activate(self, obj):
""" """
Called when the Open button is clicked, opens the DbManager Called when the Open button is clicked, opens the DbManager
""" """
import DbManager from dbman import DbManager
dialog = DbManager.DbManager(self.state, self.window) dialog = DbManager(self.dbstate, self.window)
value = dialog.run() value = dialog.run()
if value: if value:
(filename, title) = value (filename, title) = value
self.db_loader.read_file(filename) self.db_loader.read_file(filename)
self.__post_load_newdb(filename, 'x-directory/normal', title) 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)
def __post_load(self): def __post_load(self):
""" """
This method is for the common UI post_load, both new files This method is for the common UI post_load, both new files
and added data like imports. and added data like imports.
""" """
if self.state.active : if self.dbstate.active :
# clear history and fill history with first entry, active person # 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 : else :
self.uistate.clear_history(None) self.uistate.clear_history(None)
self.uistate.progress.hide() self.uistate.progress.hide()
self.state.db.undo_callback = self.__change_undo_label self.dbstate.db.undo_callback = self.__change_undo_label
self.state.db.redo_callback = self.__change_redo_label self.dbstate.db.redo_callback = self.__change_redo_label
self.__change_undo_label(None) self.__change_undo_label(None)
self.__change_redo_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.undo_history_close()
self.uistate.window.window.set_cursor(None) 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: self._post_load_newdb_nongui(filename, filetype, title)
return self._post_load_newdb_gui(filename, filetype, title)
# This method is for UI stuff when the database has changed. def _post_load_newdb_gui(self, filename, filetype, title=None):
# Window title, recent files, etc related to new file. """
Called after a new database is loaded to do GUI stuff
self.state.db.set_save_path(filename) """
# GUI related post load db stuff
# Update window title # Update window title
if filename[-1] == os.path.sep: if filename[-1] == os.path.sep:
filename = filename[:-1] filename = filename[:-1]
@ -1131,7 +1108,7 @@ class ViewManager(object):
if title: if title:
name = title name = title
if self.state.db.readonly: if self.dbstate.db.readonly:
msg = "%s (%s) - GRAMPS" % (name, _('Read Only')) msg = "%s (%s) - GRAMPS" % (name, _('Read Only'))
self.uistate.window.set_title(msg) self.uistate.window.set_title(msg)
self.actiongroup.set_sensitive(False) self.actiongroup.set_sensitive(False)
@ -1140,39 +1117,15 @@ class ViewManager(object):
self.uistate.window.set_title(msg) self.uistate.window.set_title(msg)
self.actiongroup.set_sensitive(True) 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() 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.change_page(None, None)
self.actiongroup.set_visible(True) self.actiongroup.set_visible(True)
self.readonlygroup.set_visible(True) self.readonlygroup.set_visible(True)
self.file_loaded = True
RecentFiles.recent_files(filename, name)
self.recent_manager.build() self.recent_manager.build()
# Call common __post_load # Call common __post_load method for GUI update after a change
self.__post_load() self.__post_load()
def __change_undo_label(self, label): def __change_undo_label(self, label):
@ -1240,16 +1193,16 @@ class ViewManager(object):
""" """
import Bookmarks import Bookmarks
self.bookmarks = Bookmarks.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): def add_bookmark(self, obj):
""" """
Add a bookmark to the bookmark list Add a bookmark to the bookmark list
""" """
if self.state.active: if self.dbstate.active:
self.bookmarks.add(self.state.active.get_handle()) self.bookmarks.add(self.dbstate.active.get_handle())
name = name_displayer.display(self.state.active) name = name_displayer.display(self.dbstate.active)
self.uistate.push_message(self.state, self.uistate.push_message(self.dbstate,
_("%s has been bookmarked") % name) _("%s has been bookmarked") % name)
else: else:
WarningDialog( WarningDialog(
@ -1268,7 +1221,7 @@ class ViewManager(object):
Displays the Reports dialog Displays the Reports dialog
""" """
try: try:
ReportPluginDialog(self.state, self.uistate, []) ReportPluginDialog(self.dbstate, self.uistate, [])
except Errors.WindowActiveError: except Errors.WindowActiveError:
return return
@ -1277,7 +1230,7 @@ class ViewManager(object):
Displays the Tools dialog Displays the Tools dialog
""" """
try: try:
ToolPluginDialog(self.state, self.uistate, []) ToolPluginDialog(self.dbstate, self.uistate, [])
except Errors.WindowActiveError: except Errors.WindowActiveError:
return return
@ -1287,7 +1240,7 @@ class ViewManager(object):
""" """
import ScratchPad import ScratchPad
try: try:
ScratchPad.ScratchPadWindow(self.state, self.uistate) ScratchPad.ScratchPadWindow(self.dbstate, self.uistate)
except Errors.WindowActiveError: except Errors.WindowActiveError:
return return
@ -1296,7 +1249,7 @@ class ViewManager(object):
Calls the undo function on the database Calls the undo function on the database
""" """
self.uistate.set_busy_cursor(1) self.uistate.set_busy_cursor(1)
self.state.db.undo() self.dbstate.db.undo()
self.uistate.set_busy_cursor(0) self.uistate.set_busy_cursor(0)
def redo(self, obj): def redo(self, obj):
@ -1304,7 +1257,7 @@ class ViewManager(object):
Calls the redo function on the database Calls the redo function on the database
""" """
self.uistate.set_busy_cursor(1) self.uistate.set_busy_cursor(1)
self.state.db.redo() self.dbstate.db.redo()
self.uistate.set_busy_cursor(0) self.uistate.set_busy_cursor(0)
def undo_history(self, obj): def undo_history(self, obj):
@ -1312,7 +1265,7 @@ class ViewManager(object):
Displays the Undo history window Displays the Undo history window
""" """
try: try:
self.undo_history_window = UndoHistory.UndoHistory(self.state, self.undo_history_window = UndoHistory.UndoHistory(self.dbstate,
self.uistate) self.uistate)
except Errors.WindowActiveError: except Errors.WindowActiveError:
return return
@ -1321,10 +1274,10 @@ class ViewManager(object):
""" """
Calls the ExportAssistant to export data Calls the ExportAssistant to export data
""" """
if self.state.db.db_is_open: if self.dbstate.db.db_is_open:
import ExportAssistant import ExportAssistant
try: try:
ExportAssistant.ExportAssistant(self.state, self.uistate) ExportAssistant.ExportAssistant(self.dbstate, self.uistate)
except Errors.WindowActiveError: except Errors.WindowActiveError:
return return
@ -1332,8 +1285,8 @@ class ViewManager(object):
""" """
Callback that rebuilds the tools and reports menu Callback that rebuilds the tools and reports menu
""" """
tool_menu_list = self.__pmgr.get_tool_list() tool_menu_list = self._pmgr.get_tool_list()
report_menu_list = self.__pmgr.get_report_list() report_menu_list = self._pmgr.get_report_list()
self.__build_tools_menu(tool_menu_list) self.__build_tools_menu(tool_menu_list)
self.__build_report_menu(report_menu_list) self.__build_report_menu(report_menu_list)
self.uistate.set_relationship_class() self.uistate.set_relationship_class()
@ -1403,7 +1356,7 @@ class ViewManager(object):
menu_name = ("%s...") % name[2] menu_name = ("%s...") % name[2]
ofile.write('<menuitem action="%s"/>' % new_key) ofile.write('<menuitem action="%s"/>' % new_key)
actions.append((new_key, None, menu_name, None, None, actions.append((new_key, None, menu_name, None, None,
func(name, self.state, self.uistate))) func(name, self.dbstate, self.uistate)))
ofile.write('</menu>') ofile.write('</menu>')
# If there are any unsupported items we add separator # If there are any unsupported items we add separator
@ -1418,7 +1371,7 @@ class ViewManager(object):
new_key = name[3].replace(' ', '-') new_key = name[3].replace(' ', '-')
ofile.write('<menuitem action="%s"/>' % new_key) ofile.write('<menuitem action="%s"/>' % new_key)
actions.append((new_key, None, name[2], None, None, 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>')
ofile.write('</menu></menubar></ui>') ofile.write('</menu></menubar></ui>')