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:
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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):
|
||||||
|
@ -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__ = {
|
||||||
|
@ -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()))
|
||||||
|
@ -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"""
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
256
src/cli/argparser.py
Normal 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
380
src/cli/clidbman.py
Normal 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
284
src/cli/grampscli.py
Normal 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)
|
@ -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 \
|
||||||
|
142
src/gramps.py
142
src/gramps.py
@ -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()
|
||||||
|
@ -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)
|
|
||||||
|
|
||||||
|
|
@ -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
|
||||||
|
@ -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" ]
|
||||||
|
@ -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()
|
@ -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):
|
||||||
"""
|
"""
|
@ -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>')
|
Reference in New Issue
Block a user