Compare commits
207
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ca3821f81 | ||
|
|
caf47940c7 | ||
|
|
6a78d5196d | ||
|
|
fc0fdec25c | ||
|
|
2adcc7d5a3 | ||
|
|
fcaea3e1bb | ||
|
|
fc99f12568 | ||
|
|
1e5a6e1f87 | ||
|
|
db263ff905 | ||
|
|
e202f5cd19 | ||
|
|
2fbb9878c2 | ||
|
|
ba4453213a | ||
|
|
5f133ffaf2 | ||
|
|
cbac98894b | ||
|
|
3f368b2e02 | ||
|
|
1e73ab5be7 | ||
|
|
f428069ac6 | ||
|
|
adf3772b7a | ||
|
|
46cd191e49 | ||
|
|
9afe0cc181 | ||
|
|
d3ed320636 | ||
|
|
6f4a114fdc | ||
|
|
5bec86c1f4 | ||
|
|
5b48691c3a | ||
|
|
be11466b35 | ||
|
|
0445004e7d | ||
|
|
f688d376f5 | ||
|
|
0a38a6352f | ||
|
|
2410435d8c | ||
|
|
6f1d6fa6b4 | ||
|
|
b1db542cb9 | ||
|
|
3d7479969f | ||
|
|
8f4dffa1df | ||
|
|
ffe9f31b0b | ||
|
|
b1af1c5435 | ||
|
|
94b6fcb93f | ||
|
|
4b25dde6e2 | ||
|
|
f83c077a7e | ||
|
|
323d775541 | ||
|
|
4448234281 | ||
|
|
d31ced9ca5 | ||
|
|
9706af03b4 | ||
|
|
b3ca17a90f | ||
|
|
4b13c95467 | ||
|
|
7864af3626 | ||
|
|
028f9582f5 | ||
|
|
cd32559282 | ||
|
|
3980fca903 | ||
|
|
462b0ea20b | ||
|
|
ba4b3eaef6 | ||
|
|
bf826637ab | ||
|
|
316ff4454a | ||
|
|
3266625669 | ||
|
|
90378f19c5 | ||
|
|
78fb08aa12 | ||
|
|
d081b75993 | ||
|
|
10e0e64ff4 | ||
|
|
3615e75d16 | ||
|
|
3c8cba0301 | ||
|
|
dde9ea8a90 | ||
|
|
b7c39e51c7 | ||
|
|
a216441029 | ||
|
|
89592545f3 | ||
|
|
70fff6253d | ||
|
|
84e76de2b1 | ||
|
|
fe289da983 | ||
|
|
371a8a8f89 | ||
|
|
1777cd4b29 | ||
|
|
3971fecb29 | ||
|
|
5d51155441 | ||
|
|
a63c9ad080 | ||
|
|
f44479edfb | ||
|
|
738f2b7dca | ||
|
|
f4dc24cacd | ||
|
|
eeb587e85e | ||
|
|
c93f2dd433 | ||
|
|
d4f37501a0 | ||
|
|
1b0ad55d13 | ||
|
|
aa2a451f23 | ||
|
|
59e367acf9 | ||
|
|
d8057c2b0b | ||
|
|
d624d4a0bb | ||
|
|
ac86c415a7 | ||
|
|
b26b34fba9 | ||
|
|
4ae4ac1d12 | ||
|
|
efabb77567 | ||
|
|
46eb72d002 | ||
|
|
f75a8f9497 | ||
|
|
00715fc530 | ||
|
|
e3300cd55d | ||
|
|
0f7e8e73b9 | ||
|
|
1509ba526d | ||
|
|
76084e6f52 | ||
|
|
871c1a6dea | ||
|
|
33ebd5d0b0 | ||
|
|
b9b06f1668 | ||
|
|
30f3918140 | ||
|
|
e98b065719 | ||
|
|
e7bc887f60 | ||
|
|
85014bd92c | ||
|
|
eee82ae27e | ||
|
|
adadd6c80b | ||
|
|
408caa6c0f | ||
|
|
88639fcd7d | ||
|
|
f9da7ec19a | ||
|
|
45e0b9d571 | ||
|
|
2bc4139c65 | ||
|
|
c759ee31eb | ||
|
|
12577cdfac | ||
|
|
288b381453 | ||
|
|
896b9d96e2 | ||
|
|
6b6a297a0e | ||
|
|
07ec5c757e | ||
|
|
5e31bca7d6 | ||
|
|
a91bc09160 | ||
|
|
4b7a0bafa4 | ||
|
|
3b9d38e14a | ||
|
|
df93ce3cb0 | ||
|
|
d5776d4ddb | ||
|
|
9f0376d9aa | ||
|
|
45bda3bacb | ||
|
|
ab20047e88 | ||
|
|
8544f873d4 | ||
|
|
620b2afa09 | ||
|
|
759d68a9e9 | ||
|
|
205b94ad05 | ||
|
|
c495edc220 | ||
|
|
949cd6aae6 | ||
|
|
0826ba8752 | ||
|
|
12d8b62925 | ||
|
|
9b42486f2f | ||
|
|
7e4e66d0e3 | ||
|
|
97db4cf64e | ||
|
|
455d56db1b | ||
|
|
4d614ae5e5 | ||
|
|
ac2e0a26ac | ||
|
|
0f0a9c941b | ||
|
|
89908b54b3 | ||
|
|
875242c8d6 | ||
|
|
8ef49ed303 | ||
|
|
90de264ccf | ||
|
|
700f15be75 | ||
|
|
32caee3aa4 | ||
|
|
bb6d0267ac | ||
|
|
6e24c2e79a | ||
|
|
00095667e8 | ||
|
|
da47949b60 | ||
|
|
90495324e0 | ||
|
|
50091d1f86 | ||
|
|
94a6495a06 | ||
|
|
c048ebcfbb | ||
|
|
1f9f08c3c5 | ||
|
|
05800c530a | ||
|
|
fdccc733a2 | ||
|
|
3d1095d9ca | ||
|
|
0697797504 | ||
|
|
8e34b04d64 | ||
|
|
e21a1c15d5 | ||
|
|
1aec255b38 | ||
|
|
4c268e1236 | ||
|
|
cdec0299a5 | ||
|
|
b3b46c70b2 | ||
|
|
aed7560a46 | ||
|
|
f7c9ef7dbf | ||
|
|
96f430480b | ||
|
|
e38614291d | ||
|
|
4d1bc30d64 | ||
|
|
159601d635 | ||
|
|
d7cba793cf | ||
|
|
fc92bbcdba | ||
|
|
755254cf7c | ||
|
|
5cb9c3e30e | ||
|
|
43c768c2d4 | ||
|
|
e5ce8bc829 | ||
|
|
d5ca13254f | ||
|
|
81a116b6a2 | ||
|
|
cf85f6600d | ||
|
|
84c0d69da2 | ||
|
|
4ae5c6477b | ||
|
|
41b97edb03 | ||
|
|
c9eee3b471 | ||
|
|
95376f9eff | ||
|
|
2b42dd258e | ||
|
|
e1676aca6d | ||
|
|
47ebd361d8 | ||
|
|
62aaa65958 | ||
|
|
457722811d | ||
|
|
0f9a6d57bf | ||
|
|
cd602170fd | ||
|
|
c632d0f6a9 | ||
|
|
829a8bd01d | ||
|
|
61552f6903 | ||
|
|
9643c6d620 | ||
|
|
4be86ba2ad | ||
|
|
42f5cd5d11 | ||
|
|
0000b03226 | ||
|
|
84370a75e8 | ||
|
|
9d89aaeac9 | ||
|
|
de02717139 | ||
|
|
19bfb4160a | ||
|
|
af975e926f | ||
|
|
fe45742c21 | ||
|
|
193f082f30 | ||
|
|
231b15eca1 | ||
|
|
c6f45af3f1 | ||
|
|
96cc078118 | ||
|
|
4c1a82f757 |
No files matched your search
@@ -355,4 +355,3 @@ Since Gramps generates HTML pages, you can upload the pages to your personal web
|
||||
|
||||
**** End of Gramps Frequently Asked Questions ****
|
||||
|
||||
$Id$
|
||||
@@ -33,11 +33,11 @@ all required and optional dependencies. Missing dependencies will
|
||||
result in runtime errors.
|
||||
|
||||
To build and install, whether from a tarball or git repo:
|
||||
python setup.py build
|
||||
sudo python setup.py install
|
||||
python3 setup.py build
|
||||
sudo python3 setup.py install
|
||||
|
||||
You can avoid using sudo for the install step by specifying a prefix to which you have write priviledge. The default is /usr/local, which is usually owned by root. You can learn of more options with
|
||||
python setup.py --help
|
||||
python3 setup.py --help
|
||||
|
||||
One can use gramps from the command line without installing it by
|
||||
setting the following environment variables, but that won't provide
|
||||
@@ -71,7 +71,7 @@ from the source directory.
|
||||
b) You installed Gramps, and want to start it from the PYTHONPATH. In this
|
||||
case use the command:
|
||||
|
||||
python -c 'from gramps.grampsapp import main; main()'
|
||||
python3 -c 'from gramps.grampsapp import main; main()'
|
||||
|
||||
The executable 'gramps' in /usr/local/bin or /usr/bin from a) does
|
||||
this for you.
|
||||
@@ -79,7 +79,7 @@ from the source directory.
|
||||
b) You downloaded the Gramps source code to a directory, and want to run it.
|
||||
You can start Gramps from the source code directory with
|
||||
|
||||
python Gramps.py
|
||||
python3 Gramps.py
|
||||
|
||||
See gramps/gen/const.py how Gramps finds its resource directories in case
|
||||
you encounter problems.
|
||||
@@ -90,17 +90,17 @@ If you would like to install Gramps without being root, or in an
|
||||
alternative location on windows, supply the --root argument to setup.py
|
||||
|
||||
For example:
|
||||
python setup.py install --root ~/test
|
||||
python3 setup.py install --root ~/test
|
||||
|
||||
Packager's issues
|
||||
------------------
|
||||
There is a MANIFEST.in file to indicate the work needed.
|
||||
To create a source distribution run:
|
||||
|
||||
python setup.py sdist
|
||||
python3 setup.py sdist
|
||||
|
||||
If Gramps is built outside of the source tree in a temporary location (e.g. when
|
||||
packaging for a distribution), the --resourcepath option can be used to specify
|
||||
the path to the installed location of the Gramps resources (e.g. /usr/share):
|
||||
|
||||
python setup.py install --resourcepath=/usr/share
|
||||
python3 setup.py install --resourcepath=/usr/share
|
||||
@@ -10,9 +10,11 @@ include RELEASE_NOTES
|
||||
include TODO
|
||||
include CONTRIBUTING
|
||||
include TestPlan.txt
|
||||
recursive-include bash *
|
||||
recursive-include data *
|
||||
recursive-include debian *
|
||||
recursive-include docs *
|
||||
recursive-include example *
|
||||
recursive-include gramps *
|
||||
recursive-include help *
|
||||
recursive-include images *
|
||||
|
||||
@@ -1,3 +1,199 @@
|
||||
2018-02-17
|
||||
Version 5.0.0-beta1
|
||||
* pt_PT, hu, cs, it, fi, de, ru: update translation
|
||||
* update Readme for Gramps 5.0.0
|
||||
* new genealogy tree report
|
||||
* Replace backslash in image path for Windows
|
||||
* Escape special LaTeX characters
|
||||
* Copy output file from temporary directory
|
||||
* Fix character encoding for new tree category reports.
|
||||
* Add support for new genealogy tree report category.
|
||||
* Fix Place Tree view for proper display after filter cleared.
|
||||
* Implement place formats
|
||||
* Some dates are not translated in webcal
|
||||
* Some dates are not translated in webcal
|
||||
* Webcal link problem in the year glance page
|
||||
* Link problems depending on LANGUAGES.
|
||||
* Increment year to 2018 for About box.
|
||||
* data/holidays.xml.in: Fix holidays in Finnish
|
||||
* fix Sidebar to resize better
|
||||
* mac/gramps.modules: Update pyicu to v 2.0.3
|
||||
* mac/gramps.modules: Consolidate Python2 and Python3 meta-modules.
|
||||
GObject-Introspection now works with Python3 so the split isn't needed
|
||||
any more.
|
||||
* Add tooltip to links in styled text editor.
|
||||
* [MacOS] Replace GDK_CONTROL_MASK with GDK_META_MASK in accelerators.
|
||||
* Set the mnemonic modifier key to Alt-Ctrl for the Quartz Gdk backend.
|
||||
This avoids interference with the use of the Alt/Option key for extending
|
||||
the keyboard.
|
||||
* Fix Family Lines Graph when 'unknown' places are present
|
||||
* Fix Export View to CSV when Unicode characters are present
|
||||
* Fix several intl date displayers for missing parameter.
|
||||
* mac/gramps.accel: Update gramps.accel for gramps50
|
||||
* Some filter optimizations to avoid repeated parts of tree scan
|
||||
* Fix view buttons order keeps changing on different startups
|
||||
* Fix DescendentTree report; more space beneath Title
|
||||
* Fix DescendentTree report for crashes
|
||||
* Fix Book XML handler for unusual characters in report name
|
||||
* Fix 'Generate Book' dialog for bad transient parent
|
||||
* Fix Book XML handler to deal with unusual characters in Book name
|
||||
* Add color schemes to config
|
||||
* Surname Editor; fix so changes made update Person 'Preferred Name'
|
||||
* Surname Editor, fix fields for better fit in minimum width dialogs
|
||||
* Surname Editor; fix loss of data if using mouse to change fields
|
||||
* Add Garbage collection after tools/reports
|
||||
* Main Window menu 'Add' keyboard Acellerators
|
||||
* Fix Add menu Accelerators so they don't activate on Shift-'x'
|
||||
* Change Gramps 'Add' menu so 'New' is not used
|
||||
* Export Web Family Tree; error on file write is now a message, not exception.
|
||||
* Fix Export Web Family Tree for errors on file write
|
||||
* Pylint on Web Family Tree export
|
||||
* Fix Citation Editor to Tab out of Confidence ComboBox
|
||||
* Narrated Web Site:
|
||||
* Translation problem in narrative web.
|
||||
* crash in narrative web if no unused media
|
||||
* Apply places changes to the narrativeweb.
|
||||
Also change order between locations and locations alternate names.
|
||||
* Suppress extra line in Alternate Names section.
|
||||
* Added an option for the statistics page.
|
||||
* We have problems with notes: pages are too large
|
||||
* Difficult to see some page on a mobile phone when you have notes.
|
||||
* Resizing of images on a mobile
|
||||
* Maps are too bigs on a mobile
|
||||
* Split the event line to have a narrower page
|
||||
* The note is spanned on two columns (Place + Description)
|
||||
* In media pages, the progress meter doesn't work well.
|
||||
* Add a progress meter for the medialistpage
|
||||
* warnings reported to console
|
||||
* Mainz css file produces left aligned FamilyMap pages
|
||||
Solving Mainz problem creates new ones with Basic-Blue and Nebraska
|
||||
styles sheet.
|
||||
Some cleanup with unused css file (GeoView.css)
|
||||
* Many problems with css files and the ancestor tree
|
||||
* media file not created if we have unused media.
|
||||
* Fix Narrative Web works poorly on Android browser
|
||||
* Clear old custom name formats when database loaded
|
||||
* Fix shading colour in relationship view for dark themes.
|
||||
* Fix link colour for dark themes.
|
||||
* Fix default foreground colour in list views for dark themes.
|
||||
* fix Fan Chart for exception on right-click of person with no parent
|
||||
* Fix Fan Chart Print centering and scaling on Windows
|
||||
* Fix Media Preview Gramplet for closed db
|
||||
* Added a requirement for pango and pangocairo versions
|
||||
Eliminates a warning that was present when calling a report from the
|
||||
command line.
|
||||
* Fix Styled Text Editor for exception on non-editable text click
|
||||
Also prevent editing of links on non-editable text.
|
||||
* Suport FTM 2017 Gedcom tags on import
|
||||
FTM adds subordinate PLAC and OBJE data to INDI.ADDR which is non-standard.
|
||||
So treat it as a Residence Event, instead of an Address.
|
||||
FTM puts _DATE and _TEXT tags subordinate to OBJE, also non-standard.
|
||||
* Fix Family tree Manager: rename a locked db, don't open, but title bar
|
||||
changes.
|
||||
* Fix Person, Family Sidebar Filters to add custom Event types
|
||||
Also fix Family Sidebar Filter RelType init with closed db
|
||||
* Fix ProxyCache to deal with memory leak
|
||||
* Fix duplicated method in ProxyCache
|
||||
Also changed size request to better utilize actual memory
|
||||
* Dbapi: Fix for broken 'backlinks' after a delete.
|
||||
* Dbapi: Use UPDATE instead of DELETE and INSERT
|
||||
* Remove write_version method from the database API
|
||||
This is specific to the BSDDB backend.
|
||||
* Fixes for in-memory databases
|
||||
In-memory databases always allow write access and don't need to use lock
|
||||
files.
|
||||
* Enable Sqlite backend for all users
|
||||
* Move PostgreSQL backend into third-party addons
|
||||
* Implement locking for dbapi backends
|
||||
* New convenience function to return database methods
|
||||
Examples: db.method('get_%s_from_handle', 'Person')
|
||||
db.method('get_%s_from_%s', 'Event', 'gramps_id')
|
||||
Returns None if the method doesn't exist.
|
||||
Formats 'iter_%s' and 'get_number_of_%s' use the plural forms and are
|
||||
not yet implemented. Replaces old get_table_metadata method.
|
||||
* postgresql: Add login dialog and username/password command line options
|
||||
* Write dbapi schema version to metadata table instead of a file
|
||||
* Remove hardcoded references to BSDDB database backend
|
||||
The default backend in the preferences is now used for:
|
||||
* Archive checkout
|
||||
* Import from the command line - python Gramps.py file
|
||||
* Import by drag and drop onto the family tree manager
|
||||
* db: Remove set_save_path method
|
||||
The path is already set in the database load method.
|
||||
* Add missing methods to database unit test
|
||||
* Add missing base database method.
|
||||
* db: Remove get_schema_version method.
|
||||
* db: Remove has_gramps_id method from the public API.
|
||||
* db: Remove get_gramps_ids method from the public API.
|
||||
* db: Make get_number_of, get_raw_data and has_handle methods protected.
|
||||
* db: Remove get_from_name_and_gramps_id method.
|
||||
* db: Remove get_from_name_and_handle_method.
|
||||
* postgresql: Ignore empty connection settings
|
||||
* Include new PostgreSQL error handling in CLI
|
||||
* Improve PostgreSQL error handling
|
||||
Re-raise a DbConnectionError if the connection raises an error.
|
||||
* Add connection preferences for PostgreSQL databases
|
||||
* bsddb: Build surname list with unique names.
|
||||
* fanchart2way: Use escape from html rather than cgi package
|
||||
* Fix QuestionDialog display for html like characters in title
|
||||
* Fix Find Database Loop tool for faster operation and better display
|
||||
* Fix FamilyRelationshpType _DATAMAP order to correspond with values
|
||||
* Fix HandleError on IsLessThanNthGenerationAncestorOf filters
|
||||
* Fix Relationship Graph to shows families if siblings but no parents
|
||||
* Fix Deep Relationship filter to avoid recursion crash
|
||||
This also changes filter to use shortest paths.
|
||||
* Fix to allow filter progress meters in flat views
|
||||
* Fix Gedcom import for illegal Gedcom Family Attributes
|
||||
TMG Gedcom exports an illegal NCHI with sub-data for FAM.
|
||||
Gramps could not handle this and attached the sub-data to the FAM creating
|
||||
some corrupted Event records.
|
||||
* Fix Session Log for exception on closed db reference click
|
||||
* Fix 'Welcome' Gramplet and StyledTextEditor for Link handling
|
||||
* fix Quickreport start from Editor when object not yet committed
|
||||
* Fix Find Database Loop tool for wrong transient parent for progress
|
||||
* Fix Test Case Generator to not create illegal 'None' handles
|
||||
* Fix Gedcom export for bad Hebrew Months
|
||||
* Can't link to a place if wrong lat/lon in the db.
|
||||
* Update redirected wiki link from GRAMPS_XML > Gramps_XML
|
||||
* Missing arg when right clicking in geography view.
|
||||
* INSTALL: Change INSTALL to replace 'python' with 'python3 for script invokes
|
||||
* Check&Repair; add checks for 'backlinks' and repair if needed.
|
||||
* Fix CSV importer for place event name using gramps_id
|
||||
* Fix PlaceModel to avoid exception following merge
|
||||
* Fix Geography view 'Find' when db is closed.
|
||||
* Fix interactive search for exception on click then down arrow
|
||||
* Create where_is utility to locate a binary in the standard places
|
||||
This is particularly useful on Mac OS X where Gramps is passed a PATH that
|
||||
does not include elements added by the terminal shell.
|
||||
* Fix relationship Graph so Unicode chars on Multiple pages works.
|
||||
* Use Gdk.Event.triggers_context_menu in osmgpsmaps.py.
|
||||
* Replace is_right_click implementation with Gdk.Event.triggers_context_menu().
|
||||
* Replace use of CONTROL_MASK with PRIMARY_INTENT. For better UX on Macs.
|
||||
* Fix opening recent family trees
|
||||
* Fix exception when changing a family with Add person editor open
|
||||
* Remove eval() and rename self.dbstate
|
||||
* reorderids: Another way to protect GOV IDs in Gramps ID position
|
||||
* updated German date handler: added missing Latin month names and
|
||||
some old German month names
|
||||
* remove vestage of (old) SVN source-control system
|
||||
* update authors file
|
||||
* Fix ManagedWindow/GrampsWindowManager for subsidiary window close
|
||||
* Fix Gedcom import for "1 MARR Y" issue
|
||||
* Correct ReorderID tool for several bugs and deal with GetGov ID
|
||||
* Bad call to unlink function.
|
||||
* Fix Gedcom export for erroneously removed WriterOptionBox import
|
||||
* Try to handle OSError when we use the --export argument in CLI mode.
|
||||
* Special char in place's name breaks xdot view
|
||||
* Organize Bookmarks Dialog doesn't close with x
|
||||
* tweak some DateParser comments
|
||||
* DateParserEN failures under the DateTest tool
|
||||
* Fix make_unknown to create valid Place
|
||||
* editplace: fix typos
|
||||
* StyledText.join method fails if the joint_text has StyledText Tags
|
||||
* 3.4 database conversion fails
|
||||
* fix invalid export (and import) of Gedcom EMAIL records
|
||||
Gedcom spec 5.5 requires '@' in general text to be doubled '@@'
|
||||
|
||||
2017-09-02
|
||||
Version 5.0.0-alpha3
|
||||
* Fix Undo -- crashes due to race in Gtk
|
||||
|
||||
@@ -16,14 +16,15 @@ The following packages **MUST** be installed in order for Gramps to work:
|
||||
|
||||
The following three packages with GObject Introspection bindings (the gi packages)
|
||||
|
||||
* **cairo** - a 2D graphics library with support for multiple output devices. http://cairographics.org/
|
||||
* **cairo** 1.13.1 or greater - a 2D graphics library with support for multiple output devices. http://cairographics.org/
|
||||
* **Pycairo** 1.13.3 or greater - GObject Introspection bindings for cairo. https://github.com/pygobject/pycairo
|
||||
* **pango** - a library for laying out and rendering of text, with an emphasis on internationalization. http://www.pango.org/
|
||||
* **pangocairo** - Allows you to use Pango with Cairo http://www.pango.org/
|
||||
|
||||
* **librsvg2** - (SVG icon view) a library to render SVG files using cairo. http://live.gnome.org/LibRsvg
|
||||
* **xdg-utils** - Desktop integration utilities from freedesktop.org
|
||||
* **bsddb3** - Python bindings for Oracle Berkeley DB https://pypi.python.org/pypi/bsddb3/
|
||||
|
||||
* **sqlite3** - Python bindings for SQLite Database library
|
||||
|
||||
The following package is needed for full translation of the interface
|
||||
to your language:
|
||||
@@ -44,7 +45,7 @@ The following packages are **STRONGLY RECOMMENDED** to be installed:
|
||||
It may be osmgpsmap, osm-gps-map, or python-osmgpsmap,
|
||||
but the Python bindings for this must also be present, so gir1.2-osmgpsmap-1.0.
|
||||
Without this the GeoView will not be active, see
|
||||
https://gramps-project.org/wiki/index.php?title=Gramps_4.2_Wiki_Manual_-_Categories#Geography_Category
|
||||
https://gramps-project.org/wiki/index.php?title=Gramps_5.0_Wiki_Manual_-_Categories#Geography_Category
|
||||
|
||||
* **Graphviz**
|
||||
|
||||
@@ -65,6 +66,10 @@ The following packages are **STRONGLY RECOMMENDED** to be installed:
|
||||
(These are Python bindings for the ICU package.
|
||||
https://pypi.python.org/pypi/PyICU/)
|
||||
|
||||
* **Ghostscript**
|
||||
|
||||
Used by Graphviz reports to help create PDF's
|
||||
|
||||
The following packages are optional:
|
||||
------------------------------------
|
||||
* **gtkspell**
|
||||
@@ -77,12 +82,12 @@ The following packages are optional:
|
||||
|
||||
The GNU Revision Control System (RCS) can be used to manage
|
||||
multiple revisions of your family trees. See info at
|
||||
https://gramps-project.org/wiki/index.php?title=Gramps_4.2_Wiki_Manual_-_Manage_Family_Trees#Archiving_a_Family_Tree
|
||||
https://gramps-project.org/wiki/index.php?title=Gramps_5.0_Wiki_Manual_-_Manage_Family_Trees#Archiving_a_Family_Tree
|
||||
Only rcs is needed, NO python bindings are required
|
||||
|
||||
* **PIL**
|
||||
|
||||
Python Image Library is needed to crop
|
||||
Python Image Library (PILLOW) is needed to crop
|
||||
images and also to convert non-JPG images to
|
||||
JPG so as to include them in LaTeX output.
|
||||
(For Python3 a different source may be needed,
|
||||
@@ -112,6 +117,9 @@ Prerequistes required for the following Addons to work:
|
||||
* **Graph View** - Requires: PyGoocanvas and Goocanvas (python-pygoocanvas, gir1.2-goocanvas-2.0).
|
||||
( https://gramps-project.org/wiki/index.php?title=Graph_View )
|
||||
|
||||
* **Network Chart** - Requires: networkx and pygraphviz
|
||||
( https://gramps-project.org/wiki/index.php?title=NetworkChart )
|
||||
|
||||
* **PedigreeChart** - Can optionally use - numpy if installed
|
||||
( https://gramps-project.org/wiki/index.php?title=PedigreeChart )
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
$Id$
|
||||
|
||||
UNSTABLE Gramps 4.0.0 Beta2 release.
|
||||
|
||||
|
||||
+3
-1
@@ -1,5 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- $Id$ -->
|
||||
<authors xmlns:html="http://www.w3.org/1999/xhtml">
|
||||
<author title="author">
|
||||
Alex Roitman <<html:a href="mailto:shura@gramps-project.org">shura@gramps-project.org</html:a>>
|
||||
@@ -94,6 +93,9 @@
|
||||
<author title="contributor">
|
||||
Nick Hall <<html:a href="mailto:nick__hall@hotmail.com">nick__hall@hotmail.com</html:a>>
|
||||
</author>
|
||||
<author title="contributor">
|
||||
Paul Culley <<html:a href="mailto:paulr2787@gmail.com">paulr2787@gmail.com</html:a>>
|
||||
</author>
|
||||
<author title="contributor">
|
||||
Peter Landgren <<html:a href="mailto:peter.talken@telia.com">peter.talken@telia.com</html:a>>
|
||||
</author>
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
#
|
||||
# Gramps - a GTK+/GNOME based genealogy program
|
||||
#
|
||||
# Copyright 2009 Serge Noiraud
|
||||
#
|
||||
# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
#
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
GeoView Style Sheet
|
||||
Style Name: GeoView
|
||||
Style Author: Serge Noiraud (2009)
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
This file was created for GeoView usage.
|
||||
|
||||
# $Id$
|
||||
|
||||
GeoView Styles
|
||||
|
||||
----------------------------------------------------- */
|
||||
|
||||
#geo-map
|
||||
{
|
||||
margin-left:0px;
|
||||
margin-top:0px;
|
||||
margin-right:auto;
|
||||
margin-bottom:auto;
|
||||
}
|
||||
|
||||
/* InfoWindow */
|
||||
#geo-info {
|
||||
font-size:10pt;
|
||||
max-height:200px;
|
||||
max-width: 600px;
|
||||
min-width: 300px;
|
||||
text-align: left;
|
||||
padding-right: 6px;
|
||||
overflow-x: auto;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
@@ -50,7 +50,6 @@ Characteristic of this style is that it uses the full width of the browser
|
||||
window and that horizontal highlighting bars extend over the full width of
|
||||
the page.
|
||||
--------------------------------------------------------------------------------------------------
|
||||
# $Id$
|
||||
|
||||
NarrativeWeb Styles
|
||||
------------------------------------------------------------------------------------------------
|
||||
@@ -87,6 +86,12 @@ h1 {
|
||||
font-weight: bold;
|
||||
padding-left: 15px;
|
||||
}
|
||||
h2 {
|
||||
display:block;
|
||||
margin:0;
|
||||
padding:0;
|
||||
text-align: center;
|
||||
}
|
||||
h3 {
|
||||
font-size: large;
|
||||
padding-left: 15px;
|
||||
@@ -416,7 +421,7 @@ div#SourceDetail {
|
||||
}
|
||||
.subsection {
|
||||
clear: both;
|
||||
overflow: hidden;
|
||||
overflow: visible;
|
||||
}
|
||||
.subsection p {
|
||||
margin: 0px;
|
||||
|
||||
@@ -134,7 +134,6 @@ h5, h6 {
|
||||
margin:1.3em 0 .5em 1em;
|
||||
}
|
||||
p#description {
|
||||
max-width: 800px;
|
||||
margin: 0;
|
||||
padding: 1em 20px;
|
||||
text-indent: 30px;
|
||||
@@ -289,10 +288,6 @@ table.infolist thead tr th {
|
||||
table.infolist tr td {
|
||||
border-bottom: dashed 1px #000;
|
||||
vertical-align: middle;
|
||||
padding: 6px 0 6px 10px;
|
||||
}
|
||||
table.infolist tr td a {
|
||||
display: block;
|
||||
}
|
||||
table.infolist tr.BeginLetter td, table.infolist tr.BeginSurname td {
|
||||
border-top: solid 1px #000;
|
||||
@@ -352,15 +347,15 @@ div#Individuals {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
div#Individuals table.individuallist {
|
||||
div#Individuals table.IndividualList {
|
||||
border-bottom: solid 1px #000;
|
||||
}
|
||||
div#Individuals table.individuallist tbody tr td.ColumnSurname a:hover,
|
||||
div#Individuals table.individuallist tbody tr td.ColumnSurname a:active {
|
||||
div#Individuals table.IndividualList tbody tr td.ColumnSurname a:hover,
|
||||
div#Individuals table.IndividualList tbody tr td.ColumnSurname a:active {
|
||||
cursor: default;
|
||||
background: none;
|
||||
}
|
||||
div#Individuals table.individuallist tbody tr td.ColumnName a {
|
||||
div#Individuals table.IndividualList tbody tr td.ColumnName a {
|
||||
vertical-align: middle;
|
||||
}
|
||||
div#Individuals div table.infolist tr td p {
|
||||
@@ -1130,9 +1125,10 @@ div.narrative {
|
||||
}
|
||||
.narrative p {
|
||||
font: normal .9em/1.4em sans-serif;
|
||||
margin-top: .5em;
|
||||
margin-bottom: 0;
|
||||
padding: 0 20px 1em 20px;
|
||||
margin: 0.1em 0 0.2em 0;
|
||||
}
|
||||
i + div.grampsstylednote p {
|
||||
margin: 0.1em 0 0.2em 0;
|
||||
}
|
||||
|
||||
/* Subsections : References
|
||||
|
||||
@@ -50,7 +50,6 @@ Characteristic of this style is that it uses the full width of the browser
|
||||
window and that horizontal highlighting bars extend over the full width of
|
||||
the page.
|
||||
--------------------------------------------------------------------------------------------------
|
||||
# $Id$
|
||||
|
||||
NarrativeWeb Styles
|
||||
------------------------------------------------------------------------------------------------
|
||||
@@ -87,6 +86,12 @@ h1 {
|
||||
font-weight: bold;
|
||||
padding-left: 15px;
|
||||
}
|
||||
h2 {
|
||||
display:block;
|
||||
margin:0;
|
||||
padding:0;
|
||||
text-align: center;
|
||||
}
|
||||
h3 {
|
||||
font-size: large;
|
||||
padding-left: 15px;
|
||||
@@ -466,7 +471,7 @@ div#SourceDetail {
|
||||
}
|
||||
.subsection {
|
||||
clear: both;
|
||||
overflow: hidden;
|
||||
overflow: visible;
|
||||
}
|
||||
.subsection p {
|
||||
margin: 0px;
|
||||
|
||||
@@ -50,7 +50,6 @@ Characteristic of this style is that it uses the full width of the browser
|
||||
window and that horizontal highlighting bars extend over the full width of
|
||||
the page.
|
||||
--------------------------------------------------------------------------------------------------
|
||||
# $Id$
|
||||
|
||||
NarrativeWeb Styles
|
||||
------------------------------------------------------------------------------------------------
|
||||
@@ -87,6 +86,12 @@ h1 {
|
||||
font-weight: bold;
|
||||
padding-left: 15px;
|
||||
}
|
||||
h2 {
|
||||
display:block;
|
||||
margin:0;
|
||||
padding:0;
|
||||
text-align: center;
|
||||
}
|
||||
h3 {
|
||||
font-size: large;
|
||||
padding-left: 15px;
|
||||
@@ -459,7 +464,7 @@ div#SourceDetail {
|
||||
}
|
||||
.subsection {
|
||||
clear: both;
|
||||
overflow: hidden;
|
||||
overflow: visible;
|
||||
}
|
||||
.subsection p {
|
||||
margin: 0px;
|
||||
|
||||
@@ -50,7 +50,6 @@ Characteristic of this style is that it uses the full width of the browser
|
||||
window and that horizontal highlighting bars extend over the full width of
|
||||
the page.
|
||||
--------------------------------------------------------------------------------------------------
|
||||
# $Id$
|
||||
|
||||
NarrativeWeb Styles
|
||||
------------------------------------------------------------------------------------------------
|
||||
@@ -87,6 +86,12 @@ h1 {
|
||||
font-weight: bold;
|
||||
padding-left: 15px;
|
||||
}
|
||||
h2 {
|
||||
display:block;
|
||||
margin:0;
|
||||
padding:0;
|
||||
text-align: center;
|
||||
}
|
||||
h3 {
|
||||
font-size: large;
|
||||
padding-left: 15px;
|
||||
@@ -459,7 +464,7 @@ div#SourceDetail {
|
||||
}
|
||||
.subsection {
|
||||
clear: both;
|
||||
overflow: hidden;
|
||||
overflow: visible;
|
||||
}
|
||||
.subsection p {
|
||||
margin: 0px;
|
||||
|
||||
@@ -50,7 +50,6 @@ Characteristic of this style is that it uses the full width of the browser
|
||||
window and that horizontal highlighting bars extend over the full width of
|
||||
the page.
|
||||
--------------------------------------------------------------------------------------------------
|
||||
# $Id$
|
||||
|
||||
NarrativeWeb Styles
|
||||
------------------------------------------------------------------------------------------------
|
||||
@@ -87,6 +86,12 @@ h1 {
|
||||
font-weight: bold;
|
||||
padding-left: 15px;
|
||||
}
|
||||
h2 {
|
||||
display:block;
|
||||
margin:0;
|
||||
padding:0;
|
||||
text-align: center;
|
||||
}
|
||||
h3 {
|
||||
font-size: large;
|
||||
padding-left: 15px;
|
||||
@@ -459,7 +464,7 @@ div#SourceDetail {
|
||||
}
|
||||
.subsection {
|
||||
clear: both;
|
||||
overflow: hidden;
|
||||
overflow: visible;
|
||||
}
|
||||
.subsection p {
|
||||
margin: 0px;
|
||||
|
||||
@@ -25,7 +25,6 @@ GRAMPS Cascading Style Sheet
|
||||
Style Name: Web_Citations-Outline.css
|
||||
******************************************************
|
||||
|
||||
# $Id$
|
||||
|
||||
Source Citations Outline styles
|
||||
------------------------------------------------------ */
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
#
|
||||
# $Id$
|
||||
#
|
||||
*************************************************
|
||||
GRAMPS Cascading Style Sheet
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
#
|
||||
# $Id$
|
||||
#
|
||||
*************************************************
|
||||
GRAMPS Cascading Style Sheet
|
||||
|
||||
@@ -25,7 +25,6 @@ GRAMPS Cascading Style Sheet
|
||||
Style Name: Web_Navigation-Horizontal.css
|
||||
******************************************************
|
||||
|
||||
# $Id$
|
||||
|
||||
Body Element
|
||||
------------------------------------------------------ */
|
||||
|
||||
@@ -54,7 +54,6 @@ Middle images/Web_Mainz_Mid.png
|
||||
Middle Light images/Web_Mainz_MidLight.png
|
||||
----------------------------------------------------------------------------
|
||||
|
||||
# $Id$
|
||||
|
||||
NarrativeWeb Styles
|
||||
--------------------------------------------------------------------------------------------
|
||||
@@ -68,7 +67,7 @@ body {
|
||||
background: url(../images/Web_Mainz_Bkgd.png) black repeat;
|
||||
}
|
||||
body > div {
|
||||
width: 950px;
|
||||
width: 85%;
|
||||
margin: 0px auto;
|
||||
background: url(../images/Web_Mainz_Mid.png) #FFF2C6 repeat;
|
||||
overflow: hidden;
|
||||
@@ -91,6 +90,12 @@ h1 {
|
||||
font-style: italic;
|
||||
margin-left: 2em;
|
||||
}
|
||||
h2 {
|
||||
display:block;
|
||||
margin:0;
|
||||
padding:0;
|
||||
text-align: center;
|
||||
}
|
||||
h3 {
|
||||
font-size: xx-large;
|
||||
font-weight: normal;
|
||||
@@ -144,7 +149,7 @@ div#nav ul li, #subnavigation ul li {
|
||||
float: left;
|
||||
}
|
||||
div#nav ul li a, #subnavigation ul li a {
|
||||
display: block;
|
||||
display: run-in;
|
||||
padding: 0.4em 0.8em 0.3em 0.8em;
|
||||
font-size: smaller;
|
||||
font-family: sans-serif;
|
||||
|
||||
+13
-15
@@ -45,7 +45,6 @@ gray #696969
|
||||
green #5D835F
|
||||
--------------------------------------------------------------------------------------------
|
||||
|
||||
# $Id$
|
||||
|
||||
NarrativeWeb Styles
|
||||
--------------------------------------------------------------------------------------------
|
||||
@@ -115,7 +114,7 @@ h1 {
|
||||
font:normal 2em/1.2em serif;
|
||||
}
|
||||
h2 {
|
||||
display:none;
|
||||
display:block;
|
||||
}
|
||||
h3 {
|
||||
font:normal 2em/1.2em serif;
|
||||
@@ -139,7 +138,6 @@ h5, h6 {
|
||||
|
||||
p#description {
|
||||
color:#542;
|
||||
max-width:800px;
|
||||
margin:0;
|
||||
padding:1em 20px;
|
||||
background-color:#FFF;
|
||||
@@ -278,7 +276,6 @@ table.infolist tr th a:hover {
|
||||
table.infolist tr td {
|
||||
font:normal 1.1em/1.4em serif;
|
||||
vertical-align:middle;
|
||||
padding:.1em 10px;
|
||||
}
|
||||
table.infolist tr td a {
|
||||
display:block;
|
||||
@@ -398,30 +395,30 @@ table.surname thead tr th.ColumnParents, table.surname tbody tr td.ColumnParents
|
||||
/* Individuals
|
||||
----------------------------------------------------- */
|
||||
#Individuals { }
|
||||
#Individuals table.individuallist {
|
||||
#Individuals table.IndividualList {
|
||||
border-bottom:solid 1px #A97;
|
||||
}
|
||||
#Individuals table.individuallist tbody tr td {
|
||||
#Individuals table.IndividualList tbody tr td {
|
||||
border-bottom:dashed 1px #C1B398;
|
||||
}
|
||||
#Individuals table.individuallist tbody tr td a:hover {
|
||||
#Individuals table.IndividualList tbody tr td a:hover {
|
||||
text-decoration:none;
|
||||
}
|
||||
table.individuallist tbody tr td.ColumnSurname a:hover, table.individuallist tbody tr td.ColumnSurname a:active {
|
||||
table.IndividualList tbody tr td.ColumnSurname a:hover, table.IndividualList tbody tr td.ColumnSurname a:active {
|
||||
cursor:default;
|
||||
color:black;
|
||||
background:none;
|
||||
}
|
||||
table.individuallist tbody tr td.ColumnName {
|
||||
table.IndividualList tbody tr td.ColumnName {
|
||||
padding:0;
|
||||
background-color:#FFF;
|
||||
}
|
||||
table.individuallist tbody tr td.ColumnName a {
|
||||
table.IndividualList tbody tr td.ColumnName a {
|
||||
display:block;
|
||||
padding:.6em 10px;
|
||||
padding:.1em .1em;
|
||||
vertical-align:middle;
|
||||
}
|
||||
table.individuallist tbody tr td.ColumnName a:hover {
|
||||
table.IndividualList tbody tr td.ColumnName a:hover {
|
||||
background-color:#C1B398;
|
||||
}
|
||||
#Individuals div table.infolist tr td p {
|
||||
@@ -962,11 +959,12 @@ div#Addresses table.infolist tr td a, div#Addresses table.infolist tr td p a {
|
||||
div.narrative {
|
||||
padding-bottom:0;
|
||||
}
|
||||
i + div.grampsstylednote p {
|
||||
margin: 0.1em 0 0.2em 0;
|
||||
}
|
||||
.narrative p {
|
||||
margin: 0.1em 0 0.2em 0;
|
||||
font:normal .9em/1.4em sans-serif;
|
||||
margin-top:.5em;
|
||||
margin-bottom:0;
|
||||
padding:0 20px 1em 20px;
|
||||
}
|
||||
|
||||
/* Subsections : References
|
||||
|
||||
@@ -41,7 +41,6 @@ black #000
|
||||
white #FFF
|
||||
---------------------------------------------------------------------------------------------------
|
||||
|
||||
# $Id$
|
||||
|
||||
NarrativeWeb Styles
|
||||
--------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -45,7 +45,6 @@ Unknown #000
|
||||
Males Web_Gender_Male.png
|
||||
Females Web_Gender_Female.png
|
||||
|
||||
# $Id$
|
||||
|
||||
NarrativeWeb Styles
|
||||
-------------------------------------------------------------------------------------------- */
|
||||
@@ -156,7 +155,6 @@ h5, h6 {
|
||||
margin:1.3em 0 .5em 1em;
|
||||
}
|
||||
p#description {
|
||||
max-width:800px;
|
||||
margin:0;
|
||||
padding:1em 20px;
|
||||
}
|
||||
@@ -456,30 +454,30 @@ div#Individuals {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
div#Individuals table.individuallist {
|
||||
div#Individuals table.IndividualList {
|
||||
border-bottom: solid 1px #5D835F;
|
||||
}
|
||||
div#Individuals table.individuallist tbody tr td {
|
||||
div#Individuals table.IndividualList tbody tr td {
|
||||
border-bottom: dashed 1px #5D835F;
|
||||
background-color: #D8F3D6;
|
||||
}
|
||||
div#Individuals table.individuallist tbody tr td a {
|
||||
div#Individuals table.IndividualList tbody tr td a {
|
||||
display: block;
|
||||
padding: .6em 10px;
|
||||
}
|
||||
div#Individuals table.individuallist tbody tr td.ColumnSurname a:hover,
|
||||
div#Individuals table.individuallist tbody tr td.ColumnSurname a:active {
|
||||
div#Individuals table.IndividualList tbody tr td.ColumnSurname a:hover,
|
||||
div#Individuals table.IndividualList tbody tr td.ColumnSurname a:active {
|
||||
cursor:default;
|
||||
color: #000;
|
||||
background:none;
|
||||
}
|
||||
div#Individuals table.individuallist tbody tr td.ColumnName {
|
||||
div#Individuals table.IndividualList tbody tr td.ColumnName {
|
||||
background-color: #FFF;
|
||||
}
|
||||
div#Individuals table.individuallist tbody tr td.ColumnName a {
|
||||
div#Individuals table.IndividualList tbody tr td.ColumnName a {
|
||||
vertical-align:middle;
|
||||
}
|
||||
div#Individuals table.individuallist tbody tr td.ColumnPartner {
|
||||
div#Individuals table.IndividualList tbody tr td.ColumnPartner {
|
||||
background-color: #FFF;
|
||||
}
|
||||
div#Individuals div table.infolist tr td p {
|
||||
|
||||
@@ -45,6 +45,9 @@ Females Web_Gender_Female.png
|
||||
}
|
||||
#treeContainer {
|
||||
position:relative;
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
overflow: visible;
|
||||
z-index:1;
|
||||
}
|
||||
#treeContainer div.boxbg {
|
||||
|
||||
@@ -29,7 +29,6 @@ Style Author: Stephane Charette and Jason Simanek
|
||||
Image Gallery
|
||||
-------------
|
||||
|
||||
# $Id$
|
||||
|
||||
ensure RegionBox <ol> is hidden and has no margins/padding that would shift the image */
|
||||
ol.RegionBox {
|
||||
|
||||
@@ -18,13 +18,11 @@
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
#
|
||||
# $Id$
|
||||
#
|
||||
###################################################### */
|
||||
body#FamilyMap {
|
||||
background-color: #000;
|
||||
padding: 0 14px;
|
||||
width: 965px;
|
||||
}
|
||||
|
||||
/* Family Maps
|
||||
@@ -33,8 +31,11 @@ body#FamilyMap {
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
border: solid 4px #000;
|
||||
width: 931px;
|
||||
height: 1000px;
|
||||
margin: 0px auto;
|
||||
width: 800px;
|
||||
height: 400px;
|
||||
max-width: 90%;
|
||||
max-height: 90%;
|
||||
}
|
||||
|
||||
/* Place Maps
|
||||
@@ -44,6 +45,8 @@ div#place_canvas {
|
||||
border: solid 4px #000;
|
||||
width: 500px;
|
||||
height: 400px;
|
||||
max-width: 90%;
|
||||
max-height: 90%;
|
||||
}
|
||||
button#drop {
|
||||
background-color: purple;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!-- $Id$ -->
|
||||
|
||||
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
|
||||
<mime-type type="application/x-gramps">
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
|
||||
$Id$
|
||||
-->
|
||||
<xsl:stylesheet version="1.0"
|
||||
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
# $Id$
|
||||
-->
|
||||
|
||||
<!--
|
||||
|
||||
@@ -201,12 +201,12 @@
|
||||
<date name="Pääsiäispäivä" type="religious" value="> easter(y)" />
|
||||
<date name="2. Pääsiäispäivä" type="religious" value="> easter(y)" offset="1" />
|
||||
<date name="Helatorstai" type="religious" value="> easter(y)" offset="39" if="not(y>=1973)" />
|
||||
<date name="Kristuksen taivaaseenastumisen päivä." type="religious" value="> easter(y)" offset="34" if="(y>=1973) and not(y>=1992)" />
|
||||
<date name="Kristuksen taivaaseenastumisen päivä" type="religious" value="> easter(y)" offset="34" if="(y>=1973) and not(y>=1992)" />
|
||||
<date name="Helatorstai" type="religious" value="> easter(y)" offset="39" if="(y>=1992)" />
|
||||
<date name="Helluntaipäivä" value="easter(y)" offset="49" type="religious" />
|
||||
<date name="Pyhän Kolminaisuuden päivä" value="easter(y)" offset="56" type="religious" />
|
||||
<date name="Apostolienpäivä" value="easter(y)" offset="91" type="religious" />
|
||||
<date name="Kirkastussunnuntai." value="easter(y)" offset="105" type="religious" />
|
||||
<date name="Kirkastussunnuntai" value="easter(y)" offset="105" type="religious" />
|
||||
<date name="Juhannusaatto" value="*/jun/19" offset="fri" type="religious" if="(y>=1955)" />
|
||||
<date name="Juhannusaatto" value="*/jun/23" type="religious" if="not(y>=1955)" />
|
||||
<date name="Juhannuspäivä" value="*/jun/20" offset="sat" type="religious" if="(y>=1955)" />
|
||||
@@ -224,12 +224,12 @@
|
||||
<date name="Joulupäivä" value="*/12/25" type="religious" />
|
||||
<date name="Tapaninpäivä" value="*/12/26" type="religious" />
|
||||
<date name="Johannes Evankelistan päivä" value="*/dec/27" type="religious" if="not(y>=1774)" />
|
||||
<date name="Apostoli Johanneksen päivä." value="*/dec/27" type="religious" if="dow(y, m, d) == 7 and (y>=2000)" />
|
||||
<date name="Apostoli Johanneksen päivä" value="*/dec/27" type="religious" if="dow(y, m, d) == 7 and (y>=2000)" />
|
||||
<date name="Viattomien lasten päivä" value="*/12/28" type="religious" />
|
||||
<date name="Uudenvuodenpäivä" value="*/1/1" type="national" />
|
||||
<date name="Vainojen uhrien muistopäivä" value="*/1/27" type="national" if="(y>=2002)" />
|
||||
<date name="J.L. Runebergin päivä" value="*/2/5" type="national" if="(y>=1854)" />
|
||||
<date name="Saamelaisten kansallis päivä" value="*/2/6" type="religious" if="(y>=2004)" />
|
||||
<date name="Saamelaisten kansallispäivä" value="*/2/6" type="religious" if="(y>=2004)" />
|
||||
<date name="Ystävänpäivä" value="*/2/14" type="national" if="(y>=1987)" />
|
||||
<date name="Kalevalan päivä" value="*/2/28" type="national" if="(y>=1978)" />
|
||||
<date name="Naistenpäivä" value="*/3/8" type="national" if="(y>=1975)" />
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
0 HEAD
|
||||
1 SOUR Gramps
|
||||
2 VERS 5.0.0-alpha1
|
||||
2 VERS 5.0.0-alpha2
|
||||
2 NAME Gramps
|
||||
1 DATE 29 OCT 2016
|
||||
2 TIME 15:10:31
|
||||
1 DATE 1 SEP 2017
|
||||
2 TIME 12:10:38
|
||||
1 SUBM @SUBM@
|
||||
1 FILE C:\Users\prc\AppData\Roaming\gramps\temp\exp_sample_ged.ged
|
||||
1 COPR Copyright (c) 2016 Alex Roitman,,,.
|
||||
1 COPR Copyright (c) 2017 Alex Roitman,,,.
|
||||
1 GEDC
|
||||
2 VERS 5.5.1
|
||||
2 FORM LINEAGE-LINKED
|
||||
@@ -17,7 +17,7 @@
|
||||
1 ADDR Not Provided
|
||||
2 ADR1 Not Provided
|
||||
1 PHON 666-555-4444
|
||||
1 EMAIL an_email@gmail.com
|
||||
1 EMAIL an_email@@gmail.com
|
||||
0 @I0000@ INDI
|
||||
1 NAME Anna /Hansdotter/
|
||||
2 GIVN Anna
|
||||
@@ -789,7 +789,7 @@
|
||||
1 OBJE
|
||||
2 FORM jpeg
|
||||
2 TITL Michael O'Toole 2015-11
|
||||
2 FILE c:\grampsaio64-4.9.9\share\gramps\tests\O0.jpg
|
||||
2 FILE c:\grampsaio64-5.0.0\share\gramps\tests\O0.jpg
|
||||
2 NOTE @N0019@
|
||||
1 NOTE @N0007@
|
||||
1 CHAN
|
||||
@@ -820,7 +820,7 @@
|
||||
3 CTRY USA
|
||||
2 PHON 440-871-3400
|
||||
2 PHON 800-871-3400
|
||||
2 EMAIL thetester@gmail.com
|
||||
2 EMAIL thetester@@gmail.com
|
||||
2 FAX 440-123-4567
|
||||
2 WWW http://thetester.com
|
||||
1 EVEN A very bad day
|
||||
@@ -862,7 +862,7 @@
|
||||
2 DATE 30 DEC 1954
|
||||
2 PLAC 123 Main St., Winslow, PA, 12345
|
||||
2 PHON 440-871-3401
|
||||
2 EMAIL mrstester@gmail.com
|
||||
2 EMAIL mrstester@@gmail.com
|
||||
2 FAX 440-321-4568
|
||||
2 WWW http://mrstester.com
|
||||
2 NOTE @N0011@
|
||||
@@ -879,7 +879,7 @@
|
||||
3 POST 12345
|
||||
1 PHON 440-871-3401
|
||||
1 PHON 800-871-3401
|
||||
1 EMAIL mrstester@gmail.com
|
||||
1 EMAIL mrstester@@gmail.com
|
||||
1 FAX 440-321-4568
|
||||
1 WWW http://mrstester.com
|
||||
1 NOTE @N0010@
|
||||
@@ -904,7 +904,7 @@
|
||||
3 STAE Colorado
|
||||
3 CTRY USA
|
||||
2 PHON 440-871-3402
|
||||
2 EMAIL tomtester@gmail.com
|
||||
2 EMAIL tomtester@@gmail.com
|
||||
2 FAX 440-321-4569
|
||||
2 WWW http://tomtester.com
|
||||
1 RESI
|
||||
@@ -1316,10 +1316,10 @@
|
||||
2 ADR1 360 W 4800 N, Provo, UT 84604
|
||||
1 PHON (801) 705-7000
|
||||
1 FAX (801) 705-7001
|
||||
1 EMAIL help@ancestry.com
|
||||
1 EMAIL help@@ancestry.com
|
||||
1 WWW http://www.ancestry.com
|
||||
0 @R0001@ REPO
|
||||
1 NAME SUBM (Submitter): (@SUBM@) The Subm /Tester/
|
||||
1 NAME SUBM (Submitter): (@@SUBM@@) The Subm /Tester/
|
||||
1 ADDR 123 Main St.
|
||||
2 CONT Winslow
|
||||
2 CONT PA
|
||||
@@ -1329,7 +1329,7 @@
|
||||
2 STAE PA
|
||||
2 POST 12345
|
||||
1 PHON 440-871-3401
|
||||
1 EMAIL mrstester@gmail.com
|
||||
1 EMAIL mrstester@@gmail.com
|
||||
1 FAX 440-321-4568
|
||||
1 WWW http://mrstester.com
|
||||
1 NOTE @N0009@
|
||||
@@ -1362,7 +1362,7 @@
|
||||
1 ADDR 123 High St., OSF village, CA, USA
|
||||
2 ADR1 123 High St., OSF village, CA, USA
|
||||
1 PHON 988-765-4321
|
||||
1 EMAIL tester_repo@osf.com
|
||||
1 EMAIL tester_repo@@osf.com
|
||||
1 FAX 987-654-3210
|
||||
1 WWW http://www.tester_repo.com
|
||||
1 NOTE @N0012@
|
||||
@@ -1401,8 +1401,8 @@
|
||||
1 CONT
|
||||
1 CONT Only one phone number supported Lin
|
||||
1 CONC e 9: 3 PHON (800) 705-7000
|
||||
0 @N0009@ NOTE Records not imported into SUBM (Submitter): (@SUBM@) The Subm /Test
|
||||
1 CONC er/:
|
||||
0 @N0009@ NOTE Records not imported into SUBM (Submitter): (@@SUBM@@) The Subm /Te
|
||||
1 CONC ster/:
|
||||
1 CONT
|
||||
1 CONT Only one phone number supported Lin
|
||||
1 CONC e 29: 1 PHON 800-871-3401
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
0 HEAD
|
||||
1 SOUR FTM
|
||||
2 VERS Family Tree Maker (22.0.0.1410)
|
||||
2 NAME Family Tree Maker for Windows
|
||||
1 SOUR Tester
|
||||
2 VERS 1.0
|
||||
2 NAME The Tester
|
||||
2 CORP Ancestry.com
|
||||
3 ADDR 360 W 4800 N
|
||||
4 CONT Provo, UT 84604
|
||||
@@ -44,7 +44,7 @@
|
||||
3 FORM Street, City, County, State, Country, Zip code
|
||||
2 PHON 440-871-3400
|
||||
2 PHON 800-871-3400
|
||||
2 EMAIL thetester@gmail.com
|
||||
2 EMAIL thetester@@gmail.com
|
||||
2 FAX 440-123-4567
|
||||
2 WWW http://thetester.com
|
||||
0 @I1@ INDI
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"http://gramps-project.org/xml/1.7.1/grampsxml.dtd">
|
||||
<database xmlns="http://gramps-project.org/xml/1.7.1/">
|
||||
<header>
|
||||
<created date="2016-10-23" version="5.0.0-alpha1"/>
|
||||
<created date="2017-12-19" version="5.0.0-alpha3"/>
|
||||
<researcher>
|
||||
<resname>The Subm /Tester/</resname>
|
||||
<resaddr>123 Main St.</resaddr>
|
||||
@@ -109,12 +109,12 @@
|
||||
<source handle="_0000000300000003" change="1" id="S0000">
|
||||
<stitle>Import from imp_FTM_LINK.ged</stitle>
|
||||
<sauthor>The Tester</sauthor>
|
||||
<srcattribute type="Approved system identification" value="FTM"/>
|
||||
<srcattribute type="Version number of software product" value="Family Tree Maker (22.0.0.1410)"/>
|
||||
<srcattribute type="Name of software product" value="Family Tree Maker for Windows"/>
|
||||
<srcattribute type="Approved system identification" value="Tester"/>
|
||||
<srcattribute type="Version number of software product" value="1.0"/>
|
||||
<srcattribute type="Name of software product" value="The Tester"/>
|
||||
<srcattribute type="Name of source data" value="FTM Source data"/>
|
||||
<srcattribute type="Copyright of source data" value="Copyright 2999"/>
|
||||
<srcattribute type="Generated By" value="Family Tree Maker for Windows Family Tree Maker (22.0.0.1410)"/>
|
||||
<srcattribute type="Generated By" value="The Tester 1.0"/>
|
||||
<srcattribute type="Creation date of GEDCOM" value="2016-03-01"/>
|
||||
<srcattribute type="Character set and version" value="UTF-8 1.1"/>
|
||||
<srcattribute type="GEDCOM version" value="5.5"/>
|
||||
@@ -206,7 +206,8 @@
|
||||
|
||||
Only one phone number supported Line 9: 3 PHON (800) 705-7000
|
||||
Line ignored as not understood Line 15: 2 DATE 1111-01-01
|
||||
GEDCOM FORM should be in uppercase Line 26: 2 FORM Lineage-Linked</text>
|
||||
GEDCOM FORM should be in uppercase Line 26: 2 FORM Lineage-Linked
|
||||
</text>
|
||||
<style name="fontface" value="Monospace">
|
||||
<range start="0" end="344"/>
|
||||
</style>
|
||||
@@ -214,7 +215,8 @@ GEDCOM FORM should be in uppercase Line 26:
|
||||
<note handle="_0000000600000006" change="1" id="N0001" type="GEDCOM import">
|
||||
<text>Records not imported into SUBM (Submitter): (@SUBM@) The Subm /Tester/:
|
||||
|
||||
Only one phone number supported Line 35: 1 PHON 800-871-3401</text>
|
||||
Only one phone number supported Line 35: 1 PHON 800-871-3401
|
||||
</text>
|
||||
<style name="fontface" value="Monospace">
|
||||
<range start="0" end="173"/>
|
||||
</style>
|
||||
@@ -234,7 +236,8 @@ Only one phone number supported Line 35:
|
||||
<note handle="_0000001700000017" change="1" id="N0005" type="GEDCOM import">
|
||||
<text>Records not imported into REPO (repository) Gramps ID R0002:
|
||||
|
||||
Only one phone number supported Line 93: 1 PHON 800-765-4321</text>
|
||||
Only one phone number supported Line 93: 1 PHON 800-765-4321
|
||||
</text>
|
||||
<style name="fontface" value="Monospace">
|
||||
<range start="0" end="162"/>
|
||||
</style>
|
||||
|
||||
@@ -180,6 +180,8 @@ class ArgHandler:
|
||||
self.imports = []
|
||||
self.exports = []
|
||||
self.removes = parser.removes
|
||||
self.username = parser.username
|
||||
self.password = parser.password
|
||||
|
||||
self.open = self.__handle_open_option(parser.open, parser.create)
|
||||
self.sanitize_args(parser.imports, parser.exports)
|
||||
@@ -502,10 +504,9 @@ class ArgHandler:
|
||||
self.imp_db_path = get_empty_tempdir("import_dbdir")
|
||||
dbid = config.get('database.backend')
|
||||
newdb = make_database(dbid)
|
||||
newdb.write_version(self.imp_db_path)
|
||||
|
||||
try:
|
||||
self.smgr.open_activate(self.imp_db_path)
|
||||
self.smgr.open_activate(self.imp_db_path, self.username, self.password)
|
||||
msg = _("Created empty Family Tree successfully")
|
||||
print(msg, file=sys.stderr)
|
||||
except:
|
||||
@@ -532,7 +533,7 @@ class ArgHandler:
|
||||
|
||||
# we load this file for use
|
||||
try:
|
||||
self.smgr.open_activate(self.open)
|
||||
self.smgr.open_activate(self.open, self.username, self.password)
|
||||
print(_("Opened successfully!"), file=sys.stderr)
|
||||
except:
|
||||
print(_("Error opening the file."), file=sys.stderr)
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
# Copyright (C) 2008 Brian G. Matherly
|
||||
# Copyright (C) 2012 Doug Blank
|
||||
# Copyright (C) 2012-2013 Paul Franklin
|
||||
# Copyright (C) 2017 Serge Noiraud
|
||||
#
|
||||
# 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
|
||||
@@ -35,6 +36,7 @@ Module responsible for handling the command line arguments for Gramps.
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
import sys
|
||||
import os
|
||||
import getopt
|
||||
import logging
|
||||
|
||||
@@ -60,6 +62,8 @@ Help options
|
||||
|
||||
Application options
|
||||
-O, --open=FAMILY_TREE Open Family Tree
|
||||
-U, --username=USERNAME Database username
|
||||
-P, --password=PASSWORD Database password
|
||||
-C, --create=FAMILY_TREE Create on open if new Family Tree
|
||||
-i, --import=FILENAME Import file
|
||||
-e, --export=FILENAME Export file
|
||||
@@ -136,6 +140,8 @@ class ArgParser:
|
||||
The valid options are:
|
||||
|
||||
-O, --open=FAMILY_TREE Open Family Tree
|
||||
-U, --username=USERNAME Database username
|
||||
-P, --password=PASSWORD Database password
|
||||
-C, --create=FAMILY_TREE Create on open if new Family Tree
|
||||
-i, --import=FILENAME Import file
|
||||
-e, --export=FILENAME Export file
|
||||
@@ -194,6 +200,8 @@ class ArgParser:
|
||||
|
||||
self.open_gui = None
|
||||
self.open = None
|
||||
self.username = None
|
||||
self.password = None
|
||||
self.exports = []
|
||||
self.actions = []
|
||||
self.imports = []
|
||||
@@ -277,6 +285,10 @@ class ArgParser:
|
||||
self.open = value
|
||||
elif option in ['-C', '--create']:
|
||||
self.create = value
|
||||
elif option in ['-U', '--username']:
|
||||
self.username = value
|
||||
elif option in ['-P', '--password']:
|
||||
self.password = value
|
||||
elif option in ['-i', '--import']:
|
||||
family_tree_format = None
|
||||
if (opt_ix < len(options) - 1
|
||||
@@ -290,6 +302,21 @@ class ArgParser:
|
||||
if (opt_ix < len(options) - 1
|
||||
and options[opt_ix + 1][0] in ('-f', '--format')):
|
||||
family_tree_format = options[opt_ix + 1][1]
|
||||
abs_name = os.path.abspath(os.path.expanduser(value))
|
||||
if not os.path.exists(abs_name):
|
||||
# The file doesn't exists, try to create it.
|
||||
try:
|
||||
open(abs_name, 'w').close()
|
||||
os.unlink(abs_name)
|
||||
except OSError as e:
|
||||
message = _("WARNING: %(strerr)s "
|
||||
"(errno=%(errno)s):\n"
|
||||
"WARNING: %(name)s\n") % {
|
||||
'strerr' : e.strerror,
|
||||
'errno' : e.errno,
|
||||
'name' : e.filename}
|
||||
print(message)
|
||||
sys.exit(1)
|
||||
self.exports.append((value, family_tree_format))
|
||||
elif option in ['-a', '--action']:
|
||||
action = value
|
||||
|
||||
+9
-15
@@ -68,6 +68,7 @@ _LOG = logging.getLogger(DBLOGNAME)
|
||||
#-------------------------------------------------------------------------
|
||||
DEFAULT_TITLE = _("Family Tree")
|
||||
NAME_FILE = "name.txt"
|
||||
BACKEND_FILE = "database.txt"
|
||||
META_NAME = "meta_data.db"
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
@@ -303,11 +304,13 @@ class CLIDbManager:
|
||||
name_file.write(title)
|
||||
|
||||
if create_db:
|
||||
# write the version number into metadata
|
||||
if dbid is None:
|
||||
dbid = "bsddb"
|
||||
dbid = config.get('database.backend')
|
||||
newdb = make_database(dbid)
|
||||
newdb.write_version(new_path)
|
||||
|
||||
backend_path = os.path.join(new_path, BACKEND_FILE)
|
||||
with open(backend_path, "w", encoding='utf8') as backend_file:
|
||||
backend_file.write(dbid)
|
||||
|
||||
(tval, last) = time_val(new_path)
|
||||
|
||||
@@ -355,15 +358,6 @@ class CLIDbManager:
|
||||
# write locally:
|
||||
temp_fp.write(data)
|
||||
url_fp.close()
|
||||
from gramps.gen.db.dbconst import BDBVERSFN
|
||||
# name not set
|
||||
(name, ext) = os.path.splitext(os.path.basename(filename))
|
||||
versionpath = os.path.join(name, BDBVERSFN)
|
||||
# dbase not set
|
||||
dbase = make_database("bsddb")
|
||||
_LOG.debug("Write bsddb version %s", str(dbase.version()))
|
||||
with open(versionpath, "w") as version_file:
|
||||
version_file.write(str(dbase.version()))
|
||||
temp_fp.close()
|
||||
|
||||
(name, ext) = os.path.splitext(os.path.basename(filename))
|
||||
@@ -372,13 +366,13 @@ class CLIDbManager:
|
||||
for plugin in pmgr.get_import_plugins():
|
||||
if format == plugin.get_extension():
|
||||
|
||||
new_path, name = self._create_new_db(name, edit_entry=False)
|
||||
dbid = config.get('database.backend')
|
||||
new_path, name = self._create_new_db(name, dbid=dbid,
|
||||
edit_entry=False)
|
||||
|
||||
# Create a new database
|
||||
self.__start_cursor(_("Importing data..."))
|
||||
|
||||
## Use bsddb, for now, because we assumed that above.
|
||||
dbid = "bsddb" ## config.get('database.backend')
|
||||
dbase = make_database(dbid)
|
||||
dbase.load(new_path, user.callback)
|
||||
|
||||
|
||||
+13
-13
@@ -59,7 +59,8 @@ from gramps.gen.db.exceptions import (DbUpgradeRequiredError,
|
||||
BsddbUpgradeRequiredError,
|
||||
BsddbDowngradeRequiredError,
|
||||
PythonUpgradeRequiredError,
|
||||
PythonDowngradeError)
|
||||
PythonDowngradeError,
|
||||
DbConnectionError)
|
||||
from gramps.gen.plug import BasePluginManager
|
||||
from gramps.gen.utils.config import get_researcher
|
||||
from gramps.gen.recentfiles import recent_files
|
||||
@@ -131,7 +132,7 @@ class CLIDbLoader:
|
||||
"""
|
||||
pass
|
||||
|
||||
def read_file(self, filename):
|
||||
def read_file(self, filename, username, password):
|
||||
"""
|
||||
This method takes care of changing database, and loading the data.
|
||||
In 3.0 we only allow reading of real databases of filetype
|
||||
@@ -172,8 +173,8 @@ class CLIDbLoader:
|
||||
self._begin_progress()
|
||||
|
||||
try:
|
||||
self.dbstate.db.load(filename, self._pulse_progress, mode)
|
||||
self.dbstate.db.set_save_path(filename)
|
||||
self.dbstate.db.load(filename, self._pulse_progress, mode,
|
||||
username=username, password=password)
|
||||
except DbEnvironmentError as msg:
|
||||
self.dbstate.no_database()
|
||||
self._errordialog(_("Cannot open database"), str(msg))
|
||||
@@ -201,6 +202,9 @@ class CLIDbLoader:
|
||||
except DbPythonError as msg:
|
||||
self.dbstate.no_database()
|
||||
self._errordialog(_("Cannot open database"), str(msg))
|
||||
except DbConnectionError as msg:
|
||||
self.dbstate.no_database()
|
||||
self._errordialog(_("Cannot open database"), str(msg))
|
||||
except OSError as msg:
|
||||
self.dbstate.no_database()
|
||||
self._errordialog(
|
||||
@@ -237,11 +241,11 @@ class CLIManager:
|
||||
self._pmgr = BasePluginManager.get_instance()
|
||||
self.user = user
|
||||
|
||||
def open_activate(self, path):
|
||||
def open_activate(self, path, username=None, password=None):
|
||||
"""
|
||||
Open and make a family tree active
|
||||
"""
|
||||
self._read_recent_file(path)
|
||||
self._read_recent_file(path, username, password)
|
||||
|
||||
def _errordialog(self, title, errormessage):
|
||||
"""
|
||||
@@ -250,7 +254,7 @@ class CLIManager:
|
||||
print(_('ERROR: %s') % errormessage, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
def _read_recent_file(self, filename):
|
||||
def _read_recent_file(self, filename, username=None, password=None):
|
||||
"""
|
||||
Called when a file needs to be loaded
|
||||
"""
|
||||
@@ -271,7 +275,7 @@ class CLIManager:
|
||||
"that the database is not in use."))
|
||||
return
|
||||
|
||||
if self.db_loader.read_file(filename):
|
||||
if self.db_loader.read_file(filename, username, password):
|
||||
# Attempt to figure out the database title
|
||||
path = os.path.join(filename, "name.txt")
|
||||
try:
|
||||
@@ -303,11 +307,6 @@ class CLIManager:
|
||||
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 = get_researcher()
|
||||
@@ -319,6 +318,7 @@ class CLIManager:
|
||||
and self.dbstate.db.get_total() == 0):
|
||||
self.dbstate.db.set_researcher(owner)
|
||||
|
||||
name_displayer.clear_custom_formats()
|
||||
name_displayer.set_name_format(self.dbstate.db.name_formats)
|
||||
fmt_default = config.get('preferences.name-format')
|
||||
name_displayer.set_default_format(fmt_default)
|
||||
|
||||
@@ -47,7 +47,8 @@ LOG = logging.getLogger(".")
|
||||
#-------------------------------------------------------------------------
|
||||
from gramps.gen.plug import BasePluginManager
|
||||
from gramps.gen.plug.docgen import (StyleSheet, StyleSheetList, PaperStyle,
|
||||
PAPER_PORTRAIT, PAPER_LANDSCAPE, graphdoc)
|
||||
PAPER_PORTRAIT, PAPER_LANDSCAPE, graphdoc,
|
||||
treedoc)
|
||||
from gramps.gen.plug.menu import (FamilyOption, PersonOption, NoteOption,
|
||||
MediaOption, PersonListOption, NumberOption,
|
||||
BooleanOption, DestinationOption, Option,
|
||||
@@ -56,8 +57,8 @@ from gramps.gen.plug.menu import (FamilyOption, PersonOption, NoteOption,
|
||||
from gramps.gen.display.name import displayer as name_displayer
|
||||
from gramps.gen.errors import ReportError, FilterError
|
||||
from gramps.gen.plug.report import (CATEGORY_TEXT, CATEGORY_DRAW, CATEGORY_BOOK,
|
||||
CATEGORY_GRAPHVIZ, CATEGORY_CODE,
|
||||
ReportOptions, append_styles)
|
||||
CATEGORY_GRAPHVIZ, CATEGORY_TREE,
|
||||
CATEGORY_CODE, ReportOptions, append_styles)
|
||||
from gramps.gen.plug.report._paper import paper_sizes
|
||||
from gramps.gen.const import USER_HOME, DOCGEN_OPTIONS
|
||||
from gramps.gen.dbstate import DbState
|
||||
@@ -250,6 +251,15 @@ class CommandLineReport:
|
||||
if name not in self.option_class.options_dict:
|
||||
self.option_class.options_dict[
|
||||
name] = menu.get_option_by_name(name).get_value()
|
||||
if category == CATEGORY_TREE:
|
||||
# Need to include Genealogy Tree options
|
||||
self.__toptions = treedoc.TreeOptions()
|
||||
menu = self.option_class.menu
|
||||
self.__toptions.add_menu_options(menu)
|
||||
for name in menu.get_all_option_names():
|
||||
if name not in self.option_class.options_dict:
|
||||
self.option_class.options_dict[
|
||||
name] = menu.get_option_by_name(name).get_value()
|
||||
self.option_class.load_previous_values()
|
||||
_validate_options(self.option_class, database)
|
||||
self.show = options_str_dict.pop('show', None)
|
||||
@@ -320,6 +330,10 @@ class CommandLineReport:
|
||||
for graph_format in graphdoc.FORMATS:
|
||||
self.options_help['off'][2].append(
|
||||
graph_format["type"] + "\t" + graph_format["descr"])
|
||||
elif self.category == CATEGORY_TREE:
|
||||
for tree_format in treedoc.FORMATS:
|
||||
self.options_help['off'][2].append(
|
||||
tree_format["type"] + "\t" + tree_format["descr"])
|
||||
else:
|
||||
self.options_help['off'][2] = "NA"
|
||||
|
||||
@@ -498,6 +512,15 @@ class CommandLineReport:
|
||||
# Pick the first one as the default.
|
||||
self.format = graphdoc.FORMATS[0]["class"]
|
||||
_chosen_format = graphdoc.FORMATS[0]["type"]
|
||||
elif self.category == CATEGORY_TREE:
|
||||
for tree_format in treedoc.FORMATS:
|
||||
if tree_format['type'] == self.options_dict['off']:
|
||||
if not self.format: # choose the first one, not the last
|
||||
self.format = tree_format["class"]
|
||||
if self.format is None:
|
||||
# Pick the first one as the default.
|
||||
self.format = tree_format.FORMATS[0]["class"]
|
||||
_chosen_format = tree_format.FORMATS[0]["type"]
|
||||
else:
|
||||
self.format = None
|
||||
if _chosen_format and _format_str:
|
||||
@@ -665,7 +688,7 @@ def cl_report(database, name, category, report_class, options_class,
|
||||
clr.selected_style,
|
||||
PaperStyle(clr.paper, clr.orien, clr.marginl,
|
||||
clr.marginr, clr.margint, clr.marginb))
|
||||
elif category == CATEGORY_GRAPHVIZ:
|
||||
elif category in [CATEGORY_GRAPHVIZ, CATEGORY_TREE]:
|
||||
clr.option_class.handler.doc = clr.format(
|
||||
clr.option_class,
|
||||
PaperStyle(clr.paper, clr.orien, clr.marginl,
|
||||
|
||||
+22
-20
@@ -163,6 +163,8 @@ register('database.backup-path', USER_HOME)
|
||||
register('database.backup-on-exit', True)
|
||||
register('database.autobackup', 0)
|
||||
register('database.path', os.path.join(HOME_DIR, 'grampsdb'))
|
||||
register('database.host', '')
|
||||
register('database.port', '')
|
||||
|
||||
register('export.proxy-order',
|
||||
[["privacy", 0],
|
||||
@@ -245,11 +247,8 @@ register('preferences.hide-ep-msg', False)
|
||||
register('preferences.invalid-date-format', "<b>%s</b>")
|
||||
register('preferences.iprefix', 'I%04d')
|
||||
register('preferences.name-format', 1)
|
||||
register('preferences.place-format', 0)
|
||||
register('preferences.place-auto', True)
|
||||
register('preferences.place-number', False)
|
||||
register('preferences.place-reverse', False)
|
||||
register('preferences.place-restrict', 0)
|
||||
register('preferences.place-lang', '')
|
||||
register('preferences.patronimic-surname', False)
|
||||
register('preferences.no-given-text', "[%s]" % _("Missing Given Name"))
|
||||
register('preferences.no-record-text', "[%s]" % _("Missing Record"))
|
||||
@@ -270,22 +269,25 @@ register('preferences.last-view', '')
|
||||
register('preferences.last-views', [])
|
||||
register('preferences.family-relation-type', 3) # UNKNOWN
|
||||
register('preferences.age-display-precision', 1)
|
||||
register('preferences.color-gender-male-alive', '#b8cee6')
|
||||
register('preferences.color-gender-male-death', '#b8cee6')
|
||||
register('preferences.color-gender-female-alive', '#feccf0')
|
||||
register('preferences.color-gender-female-death', '#feccf0')
|
||||
register('preferences.color-gender-unknown-alive', '#f3dbb6')
|
||||
register('preferences.color-gender-unknown-death', '#f3dbb6')
|
||||
#register('preferences.color-gender-other-alive', '#fcaf3e')
|
||||
#register('preferences.color-gender-other-death', '#fcaf3e')
|
||||
register('preferences.bordercolor-gender-male-alive', '#1f4986')
|
||||
register('preferences.bordercolor-gender-male-death', '#000000')
|
||||
register('preferences.bordercolor-gender-female-alive', '#861f69')
|
||||
register('preferences.bordercolor-gender-female-death', '#000000')
|
||||
register('preferences.bordercolor-gender-unknown-alive', '#8e5801')
|
||||
register('preferences.bordercolor-gender-unknown-death', '#000000')
|
||||
#register('preferences.bordercolor-gender-other-alive', '#f57900')
|
||||
#register('preferences.bordercolor-gender-other-death', '#000000')
|
||||
|
||||
register('colors.scheme', 0)
|
||||
register('colors.male-alive', ['#b8cee6', '#1f344a'])
|
||||
register('colors.male-dead', ['#b8cee6', '#2d3039'])
|
||||
register('colors.female-alive', ['#feccf0', '#62242D'])
|
||||
register('colors.female-dead', ['#feccf0', '#3a292b'])
|
||||
register('colors.unknown-alive', ['#f3dbb6', '#75507B'])
|
||||
register('colors.unknown-dead', ['#f3dbb6', '#35103b'])
|
||||
register('colors.family', ['#eeeeee', '#454545'])
|
||||
register('colors.family-divorced', ['#ffdede', '#5c3636'])
|
||||
register('colors.home-person', ['#bbe68a', '#304918'])
|
||||
register('colors.border-male-alive', ['#1f4986', '#171d26'])
|
||||
register('colors.border-male-dead', ['#000000', '#000000'])
|
||||
register('colors.border-female-alive', ['#861f69', '#261111'])
|
||||
register('colors.border-female-dead', ['#000000', '#000000'])
|
||||
register('colors.border-unknown-alive', ['#8e5801', '#8e5801'])
|
||||
register('colors.border-unknown-dead', ['#000000', '#000000'])
|
||||
register('colors.border-family', ['#cccccc', '#252525'])
|
||||
register('colors.border-family-divorced', ['#ff7373', '#720b0b'])
|
||||
|
||||
register('researcher.researcher-addr', '')
|
||||
register('researcher.researcher-locality', '')
|
||||
|
||||
+6
-3
@@ -112,6 +112,7 @@ VERSION_DIR = os.path.join(
|
||||
CUSTOM_FILTERS = os.path.join(VERSION_DIR, "custom_filters.xml")
|
||||
REPORT_OPTIONS = os.path.join(HOME_DIR, "report_options.xml")
|
||||
TOOL_OPTIONS = os.path.join(HOME_DIR, "tool_options.xml")
|
||||
PLACE_FORMATS = os.path.join(HOME_DIR, "place_formats.xml")
|
||||
|
||||
ENV_DIR = os.path.join(HOME_DIR, "env")
|
||||
TEMP_DIR = os.path.join(HOME_DIR, "temp")
|
||||
@@ -137,7 +138,7 @@ sys.path.insert(0, ROOT_DIR)
|
||||
git_revision = get_git_revision(ROOT_DIR).replace('\n', '')
|
||||
if sys.platform == 'win32' and git_revision == "":
|
||||
git_revision = get_git_revision(os.path.split(ROOT_DIR)[1])
|
||||
# VERSION += git_revision
|
||||
#VERSION += git_revision
|
||||
|
||||
#
|
||||
# Glade files
|
||||
@@ -216,7 +217,7 @@ GTK_GETTEXT_DOMAIN = 'gtk30'
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
COPYRIGHT_MSG = "© 2001-2006 Donald N. Allingham\n" \
|
||||
"© 2007-2017 The Gramps Developers"
|
||||
"© 2007-2018 The Gramps Developers"
|
||||
COMMENTS = _("Gramps\n (Genealogical Research and Analysis "
|
||||
"Management Programming System)\n"
|
||||
"is a personal genealogy program.")
|
||||
@@ -288,6 +289,8 @@ LONGOPTS = [
|
||||
"oaf-ior-fd=",
|
||||
"oaf-private",
|
||||
"open=",
|
||||
"username=",
|
||||
"password=",
|
||||
"create=",
|
||||
"options=",
|
||||
"screen=",
|
||||
@@ -303,7 +306,7 @@ LONGOPTS = [
|
||||
"quiet",
|
||||
]
|
||||
|
||||
SHORTOPTS = "O:C:i:e:f:a:p:d:c:r:lLthuv?syq"
|
||||
SHORTOPTS = "O:U:P:C:i:e:f:a:p:d:c:r:lLthuv?syq"
|
||||
|
||||
GRAMPS_UUID = uuid.UUID('516cd010-5a41-470f-99f8-eb22f1098ad6')
|
||||
|
||||
|
||||
@@ -58,30 +58,60 @@ class DateParserDE(DateParser):
|
||||
# Add other common latin,
|
||||
|
||||
month_to_int["januaris"] = 1
|
||||
month_to_int["ianarius"] = 1
|
||||
month_to_int["januarii"] = 1
|
||||
month_to_int["januarius"] = 1
|
||||
month_to_int["january"] = 1
|
||||
month_to_int["ianuary"] = 1
|
||||
month_to_int["mensis"] = 1
|
||||
month_to_int["februaris"] = 2
|
||||
month_to_int["februarii"] = 2
|
||||
month_to_int["februarius"] = 2
|
||||
month_to_int["february"] = 2
|
||||
month_to_int["martii"] = 3
|
||||
month_to_int["martius"] = 3
|
||||
month_to_int["martij"] = 3
|
||||
month_to_int["marty"] = 3
|
||||
month_to_int["aprilis"] = 4
|
||||
month_to_int["maius"] = 5
|
||||
month_to_int["majus"] = 5
|
||||
month_to_int["maii"] = 5
|
||||
month_to_int["maij"] = 5
|
||||
month_to_int["may"] = 5
|
||||
month_to_int["junius"] = 6
|
||||
month_to_int["iunius"] = 6
|
||||
month_to_int["junii"] = 6
|
||||
month_to_int["iunius"] = 6
|
||||
month_to_int["junij"] = 6
|
||||
month_to_int["iunij"] = 6
|
||||
month_to_int["juny"] = 6
|
||||
month_to_int["iuny"] = 6
|
||||
month_to_int["julius"] = 7
|
||||
month_to_int["iulius"] = 7
|
||||
month_to_int["julii"] = 7
|
||||
month_to_int["iulii"] = 7
|
||||
month_to_int["july"] = 7
|
||||
month_to_int["iuly"] = 7
|
||||
month_to_int["quintilis"] = 7
|
||||
month_to_int["augustus"] = 8
|
||||
month_to_int["augusti"] = 8
|
||||
month_to_int["sextilis"] = 8
|
||||
month_to_int["septembris"] = 9
|
||||
month_to_int["7ber"] = 9
|
||||
month_to_int["7bris"] = 9
|
||||
month_to_int["viiber"] = 9
|
||||
month_to_int["viibris"] = 9
|
||||
month_to_int["september"] = 9
|
||||
month_to_int["october"] = 10
|
||||
month_to_int["octobris"] = 10
|
||||
month_to_int["8bris"] = 10
|
||||
month_to_int["viiiber"] = 10
|
||||
month_to_int["viiibris"] = 10
|
||||
month_to_int["novembris"] = 11
|
||||
month_to_int["9ber"] = 11
|
||||
month_to_int["9bris"] = 11
|
||||
month_to_int["ixber"] = 11
|
||||
month_to_int["ixbris"] = 11
|
||||
month_to_int["november"] = 11
|
||||
month_to_int["decembris"] = 12
|
||||
month_to_int["10bris"] = 12
|
||||
@@ -103,38 +133,79 @@ class DateParserDE(DateParser):
|
||||
# old german names
|
||||
|
||||
month_to_int["hartung"] = 1
|
||||
month_to_int["hartmonat"] = 1
|
||||
month_to_int["hartmond"] = 1
|
||||
month_to_int["eismond"] = 1
|
||||
month_to_int["eismonat"] = 1
|
||||
month_to_int["lassmonat"] = 1
|
||||
month_to_int["wolfsmonat"] = 1
|
||||
month_to_int["wintermonat"] = 1
|
||||
month_to_int["hornung"] = 2
|
||||
month_to_int["hintester"] = 2
|
||||
month_to_int["sporkel"] = 2
|
||||
month_to_int["spörkel"] = 2
|
||||
month_to_int["rebmonat"] = 2
|
||||
month_to_int["schmelzmond"] = 2
|
||||
month_to_int["taumond"] = 2
|
||||
month_to_int["narrenmond"] = 2
|
||||
month_to_int["rebmond"] = 2
|
||||
month_to_int["hintester"] = 2
|
||||
month_to_int["letzter Wintermonat"] = 2
|
||||
month_to_int["lenzing"] = 3
|
||||
month_to_int["lenzmond"] = 3
|
||||
month_to_int["lenzmonat"] = 3
|
||||
month_to_int["frühlingsmonat"] = 3
|
||||
month_to_int["launing"] = 4
|
||||
month_to_int["grasmond"] = 4
|
||||
month_to_int["ostermond"] = 4
|
||||
month_to_int["ostermonat"] = 4
|
||||
month_to_int["beuet"] = 5
|
||||
month_to_int["blühmond"] = 5
|
||||
month_to_int["winnemond"] = 5
|
||||
month_to_int["wonnemond"] = 5
|
||||
month_to_int["wonnemonat"] = 5
|
||||
month_to_int["weidenmonat"] = 5
|
||||
month_to_int["blumenmond"] = 5
|
||||
month_to_int["brachet"] = 6
|
||||
month_to_int["brachmond"] = 6
|
||||
month_to_int["brachmonat"] = 6
|
||||
month_to_int["johannismond"] = 6
|
||||
month_to_int["weidemaent"] = 6
|
||||
month_to_int["heuet"] = 7
|
||||
month_to_int["heuert"] = 7
|
||||
month_to_int["heumond"] = 7
|
||||
month_to_int["heumonat"] = 7
|
||||
month_to_int["bärenmonat"] = 7
|
||||
month_to_int["honigmond"] = 7
|
||||
month_to_int["honigmonat"] = 7
|
||||
month_to_int["ernting"] = 8
|
||||
month_to_int["erntemond"] = 8
|
||||
month_to_int["bisemond"] = 8
|
||||
month_to_int["holzing"] = 9
|
||||
month_to_int["holzmond"] = 9
|
||||
month_to_int["scheiding"] = 9
|
||||
month_to_int["herbstmond"] = 9
|
||||
month_to_int["scheidung"] = 9
|
||||
month_to_int["erster herbstmond"] = 9
|
||||
month_to_int["herbstmonat"] = 9
|
||||
month_to_int["engelmonat"] = 9
|
||||
month_to_int["gilbhard"] = 10
|
||||
month_to_int["gilbhart"] = 10
|
||||
month_to_int["weinmond"] = 10
|
||||
month_to_int["weinmonat"] = 10
|
||||
month_to_int["zweiter herbstmond"] = 10
|
||||
month_to_int["windmond"] = 11
|
||||
month_to_int["windmonat"] = 11
|
||||
month_to_int["nebelung"] = 11
|
||||
month_to_int["nebelmond"] = 11
|
||||
month_to_int["schlachtmond"] = 11
|
||||
month_to_int["wintermond"] = 11
|
||||
month_to_int["dritter herbstmond"] = 11
|
||||
month_to_int["julmond"] = 12
|
||||
month_to_int["heilmond"] = 12
|
||||
month_to_int["christmond"] = 12
|
||||
month_to_int["christmonat"] = 12
|
||||
month_to_int["dustermond"] = 12
|
||||
month_to_int["heiligenmonat"] = 12
|
||||
month_to_int["wendeling"] = 11
|
||||
|
||||
modifier_to_int = {
|
||||
'vor' : Date.MOD_BEFORE,
|
||||
@@ -221,7 +292,7 @@ class DateDisplayDE(DateDisplay):
|
||||
)
|
||||
# this definition must agree with its "_display_gregorian" method
|
||||
|
||||
def _display_gregorian(self, date_val):
|
||||
def _display_gregorian(self, date_val, **kwargs):
|
||||
"""
|
||||
display gregorian calendar date in different format
|
||||
"""
|
||||
|
||||
@@ -155,7 +155,7 @@ class DateDisplayEL(DateDisplay):
|
||||
)
|
||||
# this definition must agree with its "_display_gregorian" method
|
||||
|
||||
def _display_gregorian(self, date_val):
|
||||
def _display_gregorian(self, date_val, **kwargs):
|
||||
"""
|
||||
display gregorian calendar date in different format
|
||||
"""
|
||||
|
||||
@@ -229,6 +229,7 @@ class DateParserHU(DateParser):
|
||||
|
||||
self._numeric = re.compile(
|
||||
"((\d+)[/\.])?\s*((\d+)[/\.])?\s*(\d+)[/\. ]?$")
|
||||
# this next RE has the (possibly-slashed) year at the string's start
|
||||
self._text2 = re.compile('((\d+)(/\d+)?\.)?\s+?%s\.?\s*(\d+\.)?\s*$'
|
||||
% self._mon_str, re.IGNORECASE)
|
||||
_span_1 = ['-tó\(ő\)l', '-tól', '-től']
|
||||
|
||||
@@ -130,6 +130,7 @@ class DateParserLT(DateParser):
|
||||
|
||||
def init_strings(self):
|
||||
DateParser.init_strings(self)
|
||||
# this next RE has the (possibly-slashed) year at the string's start
|
||||
self._text2 = re.compile('((\d+)(/\d+)?)?\s+?m\.\s+%s\s*(\d+)?\s*d?\.?$'
|
||||
% self._mon_str, re.IGNORECASE)
|
||||
_span_1 = ['nuo']
|
||||
@@ -186,7 +187,7 @@ class DateDisplayLT(DateDisplay):
|
||||
"mmmm m. mėnesio diena d.", "Mėn diena, metai")
|
||||
# this definition must agree with its "_display_gregorian" method
|
||||
|
||||
def _display_gregorian(self, date_val):
|
||||
def _display_gregorian(self, date_val, **kwargs):
|
||||
"""
|
||||
display gregorian calendar date in different format
|
||||
"""
|
||||
|
||||
@@ -164,7 +164,7 @@ class DateDisplayNL(DateDisplay):
|
||||
)
|
||||
# this definition must agree with its "_display_gregorian" method
|
||||
|
||||
def _display_gregorian(self, date_val):
|
||||
def _display_gregorian(self, date_val, **kwargs):
|
||||
"""
|
||||
display gregorian calendar date in different format
|
||||
"""
|
||||
|
||||
@@ -215,7 +215,7 @@ class DateDisplayPL(DateDisplay):
|
||||
"XII"
|
||||
)
|
||||
|
||||
def _display_gregorian(self, date_val):
|
||||
def _display_gregorian(self, date_val, **kwargs):
|
||||
"""
|
||||
display gregorian calendar date in different format
|
||||
"""
|
||||
|
||||
@@ -240,7 +240,7 @@ class DateDisplaySR_Base(DateDisplay):
|
||||
"VII", "VIII", "IX", "X", "XI", "XII"
|
||||
)
|
||||
|
||||
def _display_gregorian(self, date_val):
|
||||
def _display_gregorian(self, date_val, **kwargs):
|
||||
"""
|
||||
display gregorian calendar date in different format
|
||||
"""
|
||||
|
||||
@@ -449,8 +449,8 @@ class DateParser:
|
||||
self._modifier_after = re.compile('(.*)\s+%s' % self._mod_after_str,
|
||||
re.IGNORECASE)
|
||||
self._abt2 = re.compile('<(.*)>', re.IGNORECASE)
|
||||
self._text = re.compile('%s\.?\s+(\d+)?\s*,?\s*((\d+)(/\d+)?)?\s*$' % self._mon_str,
|
||||
re.IGNORECASE)
|
||||
self._text = re.compile('%s\.?(\s+\d+)?\s*,?\s+((\d+)(/\d+)?)?\s*$'
|
||||
% self._mon_str, re.IGNORECASE)
|
||||
# this next RE has the (possibly-slashed) year at the string's end
|
||||
self._text2 = re.compile('(\d+)?\s+?%s\.?\s*((\d+)(/\d+)?)?\s*$' % self._mon_str,
|
||||
re.IGNORECASE)
|
||||
@@ -522,7 +522,7 @@ class DateParser:
|
||||
|
||||
def _parse_calendar(self, text, regex1, regex2, mmap, check=None):
|
||||
match = regex1.match(text.lower())
|
||||
if match: # user typed in month-name day year
|
||||
if match: # user typed in 'month-name day year' or 'month-name year'
|
||||
groups = match.groups()
|
||||
if groups[0] is None:
|
||||
m = 0
|
||||
@@ -535,14 +535,16 @@ class DateParser:
|
||||
s = False
|
||||
else:
|
||||
d = self._get_int(groups[1])
|
||||
if groups[4] is not None: # slash year "/80"
|
||||
if groups[4] is not None:
|
||||
y = int(groups[3]) + 1 # fullyear + 1
|
||||
s = True
|
||||
else: # regular, non-slash date
|
||||
s = True # slash year
|
||||
else: # regular year
|
||||
y = int(groups[3])
|
||||
s = False
|
||||
value = (d, m, y, s)
|
||||
if check and not check((d, m, y)):
|
||||
if s and julian_valid(value): # slash year
|
||||
pass
|
||||
elif check and not check((d, m, y)):
|
||||
value = Date.EMPTY
|
||||
return value
|
||||
|
||||
@@ -559,10 +561,10 @@ class DateParser:
|
||||
y = None
|
||||
s = False
|
||||
else:
|
||||
if groups[2] is not None: # slash year digit
|
||||
if groups[2] is not None:
|
||||
y = int(groups[1]) + 1 # fullyear + 1
|
||||
s = True
|
||||
else: # regular, non-slash year
|
||||
s = True # slash year
|
||||
else: # regular year
|
||||
y = int(groups[1])
|
||||
s = False
|
||||
else:
|
||||
@@ -575,10 +577,10 @@ class DateParser:
|
||||
y = None
|
||||
s = False
|
||||
else:
|
||||
if groups[4] is not None: # slash year digit
|
||||
if groups[4] is not None:
|
||||
y = int(groups[3]) + 1 # fullyear + 1
|
||||
s = True
|
||||
else: # regular, non-slash year
|
||||
s = True # slash year
|
||||
else: # regular year
|
||||
y = int(groups[3])
|
||||
s = False
|
||||
value = (d, m, y, s)
|
||||
@@ -619,8 +621,8 @@ class DateParser:
|
||||
y = self._get_int(groups[0])
|
||||
m = self._get_int(groups[3])
|
||||
d = self._get_int(groups[4])
|
||||
if groups[2] and julian_valid((d, m, y + 1)): # slash year digit
|
||||
return (d, m, y + 1, True)
|
||||
if groups[2] and julian_valid((d, m, y + 1)):
|
||||
return (d, m, y + 1, True) # slash year
|
||||
if check is None or check((d, m, y)):
|
||||
return (d, m, y, False)
|
||||
return Date.EMPTY
|
||||
@@ -670,6 +672,15 @@ class DateParser:
|
||||
y = self._get_int(groups[1])
|
||||
m = self._get_int(groups[3])
|
||||
d = self._get_int(groups[4])
|
||||
if m > 12: # maybe a slash year, not a month (1722/3 is March)
|
||||
if y % 100 == 99:
|
||||
modyear = (y + 1) % 1000
|
||||
elif y % 10 == 9:
|
||||
modyear = (y + 1) % 100
|
||||
else:
|
||||
modyear = (y + 1) % 10
|
||||
if m == modyear:
|
||||
return (0, 0, y + 1, True) # slash year
|
||||
else:
|
||||
y = self._get_int(groups[4])
|
||||
if self.dmy:
|
||||
@@ -682,6 +693,15 @@ class DateParser:
|
||||
else:
|
||||
m = self._get_int(groups[1])
|
||||
d = self._get_int(groups[3])
|
||||
if m > 12: # maybe a slash year, not a month
|
||||
if m % 100 == 99:
|
||||
modyear = (m + 1) % 1000
|
||||
elif m % 10 == 9:
|
||||
modyear = (m + 1) % 100
|
||||
else:
|
||||
modyear = (m + 1) % 10
|
||||
if y == modyear:
|
||||
return (0, 0, m + 1, True) # slash year
|
||||
value = (d, m, y, False)
|
||||
if check and not check((d, m, y)):
|
||||
value = Date.EMPTY
|
||||
|
||||
+44
-24
@@ -847,12 +847,6 @@ class DbReadBase:
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_gramps_ids(self, obj_key):
|
||||
"""
|
||||
Returns all the keys from a table given a table name
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_mediapath(self):
|
||||
"""
|
||||
Return the default media path of the database.
|
||||
@@ -1024,12 +1018,6 @@ class DbReadBase:
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def has_gramps_id(self, obj_key, gramps_id):
|
||||
"""
|
||||
Returns True if the key exists in table given a table name
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def has_citation_gramps_id(self, gramps_id):
|
||||
"""
|
||||
Return True if the Gramps ID exists in the Citation table.
|
||||
@@ -1416,12 +1404,6 @@ class DbReadBase:
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def set_save_path(self, path):
|
||||
"""
|
||||
Set the save path for the database.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_dbid(self):
|
||||
"""
|
||||
A unique ID for this database on this computer.
|
||||
@@ -1434,6 +1416,50 @@ class DbReadBase:
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_summary(self):
|
||||
"""
|
||||
Returns dictionary of summary item.
|
||||
Should include, if possible:
|
||||
|
||||
_("Number of people")
|
||||
_("Version")
|
||||
_("Data version")
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def requires_login(self):
|
||||
"""
|
||||
Returns True for backends that require a login dialog, else False.
|
||||
"""
|
||||
return False
|
||||
|
||||
def method(self, fmt, *args):
|
||||
"""
|
||||
Convenience function to return database methods.
|
||||
|
||||
:param fmt: Method format string.
|
||||
:type fmt: str
|
||||
:param args: Substitutions arguments.
|
||||
:type args: str
|
||||
:returns: Returns a database method or None.
|
||||
:rtype: method
|
||||
|
||||
Examples::
|
||||
|
||||
db.method('get_%s_from_handle, 'Person')
|
||||
Returns the get_person_from_handle method.
|
||||
|
||||
db.method('get_%s_from_%s, 'Event', 'gramps_id')
|
||||
Returns the get_event_from_gramps_id method.
|
||||
|
||||
db.method('get_%s_handles, 'Attribute')
|
||||
Returns None. Attribute is not a primary object.
|
||||
|
||||
.. warning:: Formats 'iter_%s' and 'get_number_of_%s' are not yet
|
||||
implemented.
|
||||
"""
|
||||
return getattr(self, fmt % tuple([arg.lower() for arg in args]), None)
|
||||
|
||||
|
||||
class DbWriteBase(DbReadBase):
|
||||
"""
|
||||
@@ -1771,12 +1797,6 @@ class DbWriteBase(DbReadBase):
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def write_version(self, name):
|
||||
"""
|
||||
Write version number for a newly created DB.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def add_child_to_family(self, family, child,
|
||||
mrel=ChildRefType(),
|
||||
frel=ChildRefType(),
|
||||
|
||||
@@ -555,14 +555,6 @@ class DummyDb(M_A_M_B("NewBaseClass", (DbReadBase, Callback, object,), {})):
|
||||
LOG.warning("database is closed")
|
||||
return []
|
||||
|
||||
def get_gramps_ids(self, obj_key):
|
||||
"""
|
||||
Returns all the keys from a table given a table name
|
||||
"""
|
||||
if not self.db_is_open:
|
||||
LOG.warning("database is closed")
|
||||
return []
|
||||
|
||||
def get_media_attribute_types(self):
|
||||
"""
|
||||
Return a list of all Attribute types associated with Media and MediaRef
|
||||
@@ -1276,16 +1268,6 @@ class DummyDb(M_A_M_B("NewBaseClass", (DbReadBase, Callback, object,), {})):
|
||||
LOG.warning("database is closed")
|
||||
return False
|
||||
|
||||
def has_gramps_id(self, obj_key, gramps_id):
|
||||
"""
|
||||
Returns True if the key exists in table given a table name
|
||||
|
||||
Not used in current codebase
|
||||
"""
|
||||
if not self.db_is_open:
|
||||
LOG.warning("database is closed")
|
||||
return False
|
||||
|
||||
def has_name_group_key(self, name):
|
||||
"""
|
||||
Return if a key exists in the name_group table.
|
||||
@@ -1648,13 +1630,6 @@ class DummyDb(M_A_M_B("NewBaseClass", (DbReadBase, Callback, object,), {})):
|
||||
if not self.db_is_open:
|
||||
LOG.warning("database is closed")
|
||||
|
||||
def set_save_path(self, path):
|
||||
"""
|
||||
Set the save path for the database.
|
||||
"""
|
||||
if not self.db_is_open:
|
||||
LOG.warning("database is closed")
|
||||
|
||||
def get_dbid(self):
|
||||
"""
|
||||
A unique ID for this database on this computer.
|
||||
|
||||
@@ -343,6 +343,23 @@ class PythonUpgradeRequiredError(Exception):
|
||||
'db_python_version': self.db_python_version,
|
||||
'current_python_version': self.current_python_version }
|
||||
|
||||
class DbConnectionError(Exception):
|
||||
"""
|
||||
Error used to report that a database connection failed.
|
||||
"""
|
||||
def __init__(self, msg, settings_file):
|
||||
Exception.__init__(self)
|
||||
self.msg = msg
|
||||
self.settings_file = settings_file
|
||||
|
||||
def __str__(self):
|
||||
return _('Database connection failed.\n\n'
|
||||
'%(message)s\n'
|
||||
'Please check your connection settings file:\n'
|
||||
'%(settings_file)s') % {
|
||||
'message': self.msg,
|
||||
'settings_file': self.settings_file}
|
||||
|
||||
if __name__ == "__main__":
|
||||
"""
|
||||
Call this from the CLI (in order to find the imported modules):
|
||||
|
||||
+103
-117
@@ -45,12 +45,12 @@ from . import (DbReadBase, DbWriteBase, DbUndo, DBLOGNAME, DBUNDOFN,
|
||||
KEY_TO_CLASS_MAP, REFERENCE_KEY, PERSON_KEY, FAMILY_KEY,
|
||||
CITATION_KEY, SOURCE_KEY, EVENT_KEY, MEDIA_KEY, PLACE_KEY,
|
||||
REPOSITORY_KEY, NOTE_KEY, TAG_KEY, TXNADD, TXNUPD, TXNDEL,
|
||||
KEY_TO_NAME_MAP)
|
||||
KEY_TO_NAME_MAP, DBMODE_R, DBMODE_W)
|
||||
from .utils import write_lock_file, clear_lock_file
|
||||
from ..errors import HandleError
|
||||
from ..utils.callback import Callback
|
||||
from ..updatecallback import UpdateCallback
|
||||
from .bookmarks import DbBookmarks
|
||||
from . import exceptions
|
||||
|
||||
from ..utils.id import create_id
|
||||
from ..lib.researcher import Researcher
|
||||
@@ -245,7 +245,7 @@ class DbGenericUndo(DbUndo):
|
||||
sql = "DELETE FROM %s WHERE handle = ?" % table
|
||||
self.db.dbapi.execute(sql, [handle])
|
||||
else:
|
||||
if self.db.has_handle(obj_key, handle):
|
||||
if self.db._has_handle(obj_key, handle):
|
||||
sql = "UPDATE %s SET blob_data = ? WHERE handle = ?" % table
|
||||
self.db.dbapi.execute(sql, [pickle.dumps(data), handle])
|
||||
else:
|
||||
@@ -529,7 +529,6 @@ class DbGeneric(DbWriteBase, DbReadBase, UpdateCallback, Callback):
|
||||
"del_func": self.remove_tag,
|
||||
}
|
||||
}
|
||||
self.set_save_path(directory)
|
||||
self.readonly = False
|
||||
self.db_is_open = False
|
||||
self.name_formats = []
|
||||
@@ -577,34 +576,60 @@ class DbGeneric(DbWriteBase, DbReadBase, UpdateCallback, Callback):
|
||||
if directory:
|
||||
self.load(directory)
|
||||
|
||||
def _initialize(self, directory):
|
||||
def _initialize(self, directory, username, password):
|
||||
"""
|
||||
Initialize database backend.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def load(self, directory, callback=None, mode=None,
|
||||
def __check_readonly(self, name):
|
||||
"""
|
||||
Return True if we don't have read/write access to the database,
|
||||
otherwise return False (that is, we DO have read/write access)
|
||||
"""
|
||||
# In-memory databases always allow write access.
|
||||
if name == ':memory:':
|
||||
return False
|
||||
|
||||
# See if we write to the target directory at all?
|
||||
if not os.access(name, os.W_OK):
|
||||
return True
|
||||
|
||||
# See if we lack write access to the database file
|
||||
path = os.path.join(name, 'sqlite.db')
|
||||
if os.path.isfile(path) and not os.access(path, os.W_OK):
|
||||
return True
|
||||
|
||||
# All tests passed. Inform caller that we are NOT read only
|
||||
return False
|
||||
|
||||
def load(self, directory, callback=None, mode=DBMODE_W,
|
||||
force_schema_upgrade=False,
|
||||
force_bsddb_upgrade=False,
|
||||
force_bsddb_downgrade=False,
|
||||
force_python_upgrade=False,
|
||||
update=True):
|
||||
update=True,
|
||||
username=None,
|
||||
password=None):
|
||||
"""
|
||||
If update is False: then don't update any files
|
||||
"""
|
||||
db_schema_version = self.get_schema_version(directory)
|
||||
current_schema_version = self.VERSION[0]
|
||||
if db_schema_version != current_schema_version:
|
||||
raise exceptions.DbVersionError(str(db_schema_version),
|
||||
str(current_schema_version),
|
||||
str(current_schema_version))
|
||||
if self.__check_readonly(directory):
|
||||
mode = DBMODE_R
|
||||
|
||||
self.readonly = mode == DBMODE_R
|
||||
|
||||
if not self.readonly and directory != ':memory:':
|
||||
write_lock_file(directory)
|
||||
|
||||
# run backend-specific code:
|
||||
self._initialize(directory)
|
||||
self._initialize(directory, username, password)
|
||||
|
||||
# We use the existence of the person table as a proxy for the database
|
||||
# being new
|
||||
if not self.dbapi.table_exists("person"):
|
||||
self._create_schema()
|
||||
self._set_metadata('version', str(self.VERSION[0]))
|
||||
|
||||
# Load metadata
|
||||
self.name_formats = self._get_metadata('name_formats')
|
||||
@@ -643,7 +668,8 @@ class DbGeneric(DbWriteBase, DbReadBase, UpdateCallback, Callback):
|
||||
# surname list
|
||||
self.surname_list = self.get_surname_list()
|
||||
|
||||
self.set_save_path(directory)
|
||||
self._set_save_path(directory)
|
||||
|
||||
if self._directory:
|
||||
self.undolog = os.path.join(self._directory, DBUNDOFN)
|
||||
else:
|
||||
@@ -739,6 +765,12 @@ class DbGeneric(DbWriteBase, DbReadBase, UpdateCallback, Callback):
|
||||
self._set_metadata('nmap_index', self.nmap_index)
|
||||
|
||||
self._close()
|
||||
|
||||
try:
|
||||
clear_lock_file(self.get_save_path())
|
||||
except IOError:
|
||||
pass
|
||||
|
||||
self.db_is_open = False
|
||||
self._directory = None
|
||||
|
||||
@@ -770,13 +802,6 @@ class DbGeneric(DbWriteBase, DbReadBase, UpdateCallback, Callback):
|
||||
"""Return True when the file has a supported version."""
|
||||
return True
|
||||
|
||||
def get_schema_version(self, directory=None):
|
||||
"""
|
||||
Get the version of the schema that the database was created
|
||||
under. Assumes 18, if not found.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def _get_table_func(self, table=None, func=None):
|
||||
"""
|
||||
Private implementation of get_table_func.
|
||||
@@ -790,45 +815,6 @@ class DbGeneric(DbWriteBase, DbReadBase, UpdateCallback, Callback):
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_table_names(self):
|
||||
"""Return a list of valid table names."""
|
||||
return list(self._get_table_func())
|
||||
|
||||
def get_table_metadata(self, table_name):
|
||||
"""Return the metadata for a valid table name."""
|
||||
if table_name in self._get_table_func():
|
||||
return self._get_table_func(table_name)
|
||||
return None
|
||||
|
||||
def get_from_name_and_handle(self, table_name, handle):
|
||||
"""
|
||||
Returns a gen.lib object (or None) given table_name and
|
||||
handle.
|
||||
|
||||
Examples:
|
||||
|
||||
>>> self.get_from_name_and_handle("Person", "a7ad62365bc652387008")
|
||||
>>> self.get_from_name_and_handle("Media", "c3434653675bcd736f23")
|
||||
"""
|
||||
if table_name in self._get_table_func() and handle:
|
||||
return self._get_table_func(table_name, "handle_func")(handle)
|
||||
return None
|
||||
|
||||
def get_from_name_and_gramps_id(self, table_name, gramps_id):
|
||||
"""
|
||||
Returns a gen.lib object (or None) given table_name and
|
||||
Gramps ID.
|
||||
|
||||
Examples:
|
||||
|
||||
>>> self.get_from_name_and_gramps_id("Person", "I00002")
|
||||
>>> self.get_from_name_and_gramps_id("Family", "F056")
|
||||
>>> self.get_from_name_and_gramps_id("Media", "M00012")
|
||||
"""
|
||||
if table_name in self._get_table_func():
|
||||
return self._get_table_func(table_name, "gramps_id_func")(gramps_id)
|
||||
return None
|
||||
|
||||
def _txn_begin(self):
|
||||
"""
|
||||
Lowlevel interface to the backend transaction.
|
||||
@@ -1050,7 +1036,7 @@ class DbGeneric(DbWriteBase, DbReadBase, UpdateCallback, Callback):
|
||||
Helper function for find_next_<object>_gramps_id methods
|
||||
"""
|
||||
index = prefix % map_index
|
||||
while self.has_gramps_id(obj_key, index):
|
||||
while self._has_gramps_id(obj_key, index):
|
||||
map_index += 1
|
||||
index = prefix % map_index
|
||||
map_index += 1
|
||||
@@ -1152,7 +1138,7 @@ class DbGeneric(DbWriteBase, DbReadBase, UpdateCallback, Callback):
|
||||
#
|
||||
################################################################
|
||||
|
||||
def get_number_of(self, obj_key):
|
||||
def _get_number_of(self, obj_key):
|
||||
"""
|
||||
Return the number of objects currently in the database.
|
||||
"""
|
||||
@@ -1162,61 +1148,61 @@ class DbGeneric(DbWriteBase, DbReadBase, UpdateCallback, Callback):
|
||||
"""
|
||||
Return the number of people currently in the database.
|
||||
"""
|
||||
return self.get_number_of(PERSON_KEY)
|
||||
return self._get_number_of(PERSON_KEY)
|
||||
|
||||
def get_number_of_events(self):
|
||||
"""
|
||||
Return the number of events currently in the database.
|
||||
"""
|
||||
return self.get_number_of(EVENT_KEY)
|
||||
return self._get_number_of(EVENT_KEY)
|
||||
|
||||
def get_number_of_places(self):
|
||||
"""
|
||||
Return the number of places currently in the database.
|
||||
"""
|
||||
return self.get_number_of(PLACE_KEY)
|
||||
return self._get_number_of(PLACE_KEY)
|
||||
|
||||
def get_number_of_tags(self):
|
||||
"""
|
||||
Return the number of tags currently in the database.
|
||||
"""
|
||||
return self.get_number_of(TAG_KEY)
|
||||
return self._get_number_of(TAG_KEY)
|
||||
|
||||
def get_number_of_families(self):
|
||||
"""
|
||||
Return the number of families currently in the database.
|
||||
"""
|
||||
return self.get_number_of(FAMILY_KEY)
|
||||
return self._get_number_of(FAMILY_KEY)
|
||||
|
||||
def get_number_of_notes(self):
|
||||
"""
|
||||
Return the number of notes currently in the database.
|
||||
"""
|
||||
return self.get_number_of(NOTE_KEY)
|
||||
return self._get_number_of(NOTE_KEY)
|
||||
|
||||
def get_number_of_citations(self):
|
||||
"""
|
||||
Return the number of citations currently in the database.
|
||||
"""
|
||||
return self.get_number_of(CITATION_KEY)
|
||||
return self._get_number_of(CITATION_KEY)
|
||||
|
||||
def get_number_of_sources(self):
|
||||
"""
|
||||
Return the number of sources currently in the database.
|
||||
"""
|
||||
return self.get_number_of(SOURCE_KEY)
|
||||
return self._get_number_of(SOURCE_KEY)
|
||||
|
||||
def get_number_of_media(self):
|
||||
"""
|
||||
Return the number of media objects currently in the database.
|
||||
"""
|
||||
return self.get_number_of(MEDIA_KEY)
|
||||
return self._get_number_of(MEDIA_KEY)
|
||||
|
||||
def get_number_of_repositories(self):
|
||||
"""
|
||||
Return the number of source repositories currently in the database.
|
||||
"""
|
||||
return self.get_number_of(REPOSITORY_KEY)
|
||||
return self._get_number_of(REPOSITORY_KEY)
|
||||
|
||||
################################################################
|
||||
#
|
||||
@@ -1224,7 +1210,7 @@ class DbGeneric(DbWriteBase, DbReadBase, UpdateCallback, Callback):
|
||||
#
|
||||
################################################################
|
||||
|
||||
def get_gramps_ids(self, obj_key):
|
||||
def _get_gramps_ids(self, obj_key):
|
||||
"""
|
||||
Return a list of Gramps IDs, one ID for each object in the
|
||||
database.
|
||||
@@ -1236,63 +1222,63 @@ class DbGeneric(DbWriteBase, DbReadBase, UpdateCallback, Callback):
|
||||
Return a list of Gramps IDs, one ID for each Person in the
|
||||
database.
|
||||
"""
|
||||
return self.get_gramps_ids(PERSON_KEY)
|
||||
return self._get_gramps_ids(PERSON_KEY)
|
||||
|
||||
def get_family_gramps_ids(self):
|
||||
"""
|
||||
Return a list of Gramps IDs, one ID for each Family in the
|
||||
database.
|
||||
"""
|
||||
return self.get_gramps_ids(FAMILY_KEY)
|
||||
return self._get_gramps_ids(FAMILY_KEY)
|
||||
|
||||
def get_source_gramps_ids(self):
|
||||
"""
|
||||
Return a list of Gramps IDs, one ID for each Source in the
|
||||
database.
|
||||
"""
|
||||
return self.get_gramps_ids(SOURCE_KEY)
|
||||
return self._get_gramps_ids(SOURCE_KEY)
|
||||
|
||||
def get_citation_gramps_ids(self):
|
||||
"""
|
||||
Return a list of Gramps IDs, one ID for each Citation in the
|
||||
database.
|
||||
"""
|
||||
return self.get_gramps_ids(CITATION_KEY)
|
||||
return self._get_gramps_ids(CITATION_KEY)
|
||||
|
||||
def get_event_gramps_ids(self):
|
||||
"""
|
||||
Return a list of Gramps IDs, one ID for each Event in the
|
||||
database.
|
||||
"""
|
||||
return self.get_gramps_ids(EVENT_KEY)
|
||||
return self._get_gramps_ids(EVENT_KEY)
|
||||
|
||||
def get_media_gramps_ids(self):
|
||||
"""
|
||||
Return a list of Gramps IDs, one ID for each Media in the
|
||||
database.
|
||||
"""
|
||||
return self.get_gramps_ids(MEDIA_KEY)
|
||||
return self._get_gramps_ids(MEDIA_KEY)
|
||||
|
||||
def get_place_gramps_ids(self):
|
||||
"""
|
||||
Return a list of Gramps IDs, one ID for each Place in the
|
||||
database.
|
||||
"""
|
||||
return self.get_gramps_ids(PLACE_KEY)
|
||||
return self._get_gramps_ids(PLACE_KEY)
|
||||
|
||||
def get_repository_gramps_ids(self):
|
||||
"""
|
||||
Return a list of Gramps IDs, one ID for each Repository in the
|
||||
database.
|
||||
"""
|
||||
return self.get_gramps_ids(REPOSITORY_KEY)
|
||||
return self._get_gramps_ids(REPOSITORY_KEY)
|
||||
|
||||
def get_note_gramps_ids(self):
|
||||
"""
|
||||
Return a list of Gramps IDs, one ID for each Note in the
|
||||
database.
|
||||
"""
|
||||
return self.get_gramps_ids(NOTE_KEY)
|
||||
return self._get_gramps_ids(NOTE_KEY)
|
||||
|
||||
################################################################
|
||||
#
|
||||
@@ -1305,7 +1291,7 @@ class DbGeneric(DbWriteBase, DbReadBase, UpdateCallback, Callback):
|
||||
raise HandleError('Handle is None')
|
||||
if not handle:
|
||||
raise HandleError('Handle is empty')
|
||||
data = self.get_raw_data(obj_key, handle)
|
||||
data = self._get_raw_data(obj_key, handle)
|
||||
if data:
|
||||
return obj_class.create(data)
|
||||
else:
|
||||
@@ -1389,41 +1375,41 @@ class DbGeneric(DbWriteBase, DbReadBase, UpdateCallback, Callback):
|
||||
#
|
||||
################################################################
|
||||
|
||||
def has_handle(self, obj_key, handle):
|
||||
def _has_handle(self, obj_key, handle):
|
||||
"""
|
||||
Return True if the handle exists in the database.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def has_person_handle(self, handle):
|
||||
return self.has_handle(PERSON_KEY, handle)
|
||||
return self._has_handle(PERSON_KEY, handle)
|
||||
|
||||
def has_family_handle(self, handle):
|
||||
return self.has_handle(FAMILY_KEY, handle)
|
||||
return self._has_handle(FAMILY_KEY, handle)
|
||||
|
||||
def has_source_handle(self, handle):
|
||||
return self.has_handle(SOURCE_KEY, handle)
|
||||
return self._has_handle(SOURCE_KEY, handle)
|
||||
|
||||
def has_citation_handle(self, handle):
|
||||
return self.has_handle(CITATION_KEY, handle)
|
||||
return self._has_handle(CITATION_KEY, handle)
|
||||
|
||||
def has_event_handle(self, handle):
|
||||
return self.has_handle(EVENT_KEY, handle)
|
||||
return self._has_handle(EVENT_KEY, handle)
|
||||
|
||||
def has_media_handle(self, handle):
|
||||
return self.has_handle(MEDIA_KEY, handle)
|
||||
return self._has_handle(MEDIA_KEY, handle)
|
||||
|
||||
def has_place_handle(self, handle):
|
||||
return self.has_handle(PLACE_KEY, handle)
|
||||
return self._has_handle(PLACE_KEY, handle)
|
||||
|
||||
def has_repository_handle(self, handle):
|
||||
return self.has_handle(REPOSITORY_KEY, handle)
|
||||
return self._has_handle(REPOSITORY_KEY, handle)
|
||||
|
||||
def has_note_handle(self, handle):
|
||||
return self.has_handle(NOTE_KEY, handle)
|
||||
return self._has_handle(NOTE_KEY, handle)
|
||||
|
||||
def has_tag_handle(self, handle):
|
||||
return self.has_handle(TAG_KEY, handle)
|
||||
return self._has_handle(TAG_KEY, handle)
|
||||
|
||||
################################################################
|
||||
#
|
||||
@@ -1431,35 +1417,35 @@ class DbGeneric(DbWriteBase, DbReadBase, UpdateCallback, Callback):
|
||||
#
|
||||
################################################################
|
||||
|
||||
def has_gramps_id(self, obj_key, gramps_id):
|
||||
def _has_gramps_id(self, obj_key, gramps_id):
|
||||
raise NotImplementedError
|
||||
|
||||
def has_person_gramps_id(self, gramps_id):
|
||||
return self.has_gramps_id(PERSON_KEY, gramps_id)
|
||||
return self._has_gramps_id(PERSON_KEY, gramps_id)
|
||||
|
||||
def has_family_gramps_id(self, gramps_id):
|
||||
return self.has_gramps_id(FAMILY_KEY, gramps_id)
|
||||
return self._has_gramps_id(FAMILY_KEY, gramps_id)
|
||||
|
||||
def has_source_gramps_id(self, gramps_id):
|
||||
return self.has_gramps_id(SOURCE_KEY, gramps_id)
|
||||
return self._has_gramps_id(SOURCE_KEY, gramps_id)
|
||||
|
||||
def has_citation_gramps_id(self, gramps_id):
|
||||
return self.has_gramps_id(CITATION_KEY, gramps_id)
|
||||
return self._has_gramps_id(CITATION_KEY, gramps_id)
|
||||
|
||||
def has_event_gramps_id(self, gramps_id):
|
||||
return self.has_gramps_id(EVENT_KEY, gramps_id)
|
||||
return self._has_gramps_id(EVENT_KEY, gramps_id)
|
||||
|
||||
def has_media_gramps_id(self, gramps_id):
|
||||
return self.has_gramps_id(MEDIA_KEY, gramps_id)
|
||||
return self._has_gramps_id(MEDIA_KEY, gramps_id)
|
||||
|
||||
def has_place_gramps_id(self, gramps_id):
|
||||
return self.has_gramps_id(PLACE_KEY, gramps_id)
|
||||
return self._has_gramps_id(PLACE_KEY, gramps_id)
|
||||
|
||||
def has_repository_gramps_id(self, gramps_id):
|
||||
return self.has_gramps_id(REPOSITORY_KEY, gramps_id)
|
||||
return self._has_gramps_id(REPOSITORY_KEY, gramps_id)
|
||||
|
||||
def has_note_gramps_id(self, gramps_id):
|
||||
return self.has_gramps_id(NOTE_KEY, gramps_id)
|
||||
return self._has_gramps_id(NOTE_KEY, gramps_id)
|
||||
|
||||
################################################################
|
||||
#
|
||||
@@ -1695,41 +1681,41 @@ class DbGeneric(DbWriteBase, DbReadBase, UpdateCallback, Callback):
|
||||
#
|
||||
################################################################
|
||||
|
||||
def get_raw_data(self, obj_key, handle):
|
||||
def _get_raw_data(self, obj_key, handle):
|
||||
"""
|
||||
Return raw (serialized and pickled) object from handle.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_raw_person_data(self, handle):
|
||||
return self.get_raw_data(PERSON_KEY, handle)
|
||||
return self._get_raw_data(PERSON_KEY, handle)
|
||||
|
||||
def get_raw_family_data(self, handle):
|
||||
return self.get_raw_data(FAMILY_KEY, handle)
|
||||
return self._get_raw_data(FAMILY_KEY, handle)
|
||||
|
||||
def get_raw_source_data(self, handle):
|
||||
return self.get_raw_data(SOURCE_KEY, handle)
|
||||
return self._get_raw_data(SOURCE_KEY, handle)
|
||||
|
||||
def get_raw_citation_data(self, handle):
|
||||
return self.get_raw_data(CITATION_KEY, handle)
|
||||
return self._get_raw_data(CITATION_KEY, handle)
|
||||
|
||||
def get_raw_event_data(self, handle):
|
||||
return self.get_raw_data(EVENT_KEY, handle)
|
||||
return self._get_raw_data(EVENT_KEY, handle)
|
||||
|
||||
def get_raw_media_data(self, handle):
|
||||
return self.get_raw_data(MEDIA_KEY, handle)
|
||||
return self._get_raw_data(MEDIA_KEY, handle)
|
||||
|
||||
def get_raw_place_data(self, handle):
|
||||
return self.get_raw_data(PLACE_KEY, handle)
|
||||
return self._get_raw_data(PLACE_KEY, handle)
|
||||
|
||||
def get_raw_repository_data(self, handle):
|
||||
return self.get_raw_data(REPOSITORY_KEY, handle)
|
||||
return self._get_raw_data(REPOSITORY_KEY, handle)
|
||||
|
||||
def get_raw_note_data(self, handle):
|
||||
return self.get_raw_data(NOTE_KEY, handle)
|
||||
return self._get_raw_data(NOTE_KEY, handle)
|
||||
|
||||
def get_raw_tag_data(self, handle):
|
||||
return self.get_raw_data(TAG_KEY, handle)
|
||||
return self._get_raw_data(TAG_KEY, handle)
|
||||
|
||||
################################################################
|
||||
#
|
||||
@@ -2414,7 +2400,7 @@ class DbGeneric(DbWriteBase, DbReadBase, UpdateCallback, Callback):
|
||||
def get_save_path(self):
|
||||
return self._directory
|
||||
|
||||
def set_save_path(self, directory):
|
||||
def _set_save_path(self, directory):
|
||||
self._directory = directory
|
||||
if directory:
|
||||
self.full_name = os.path.abspath(self._directory)
|
||||
|
||||
+34
-1
@@ -39,8 +39,9 @@ import logging
|
||||
#------------------------------------------------------------------------
|
||||
from ..plug import BasePluginManager
|
||||
from ..const import PLUGINS_DIR, USER_PLUGINS
|
||||
from ..constfunc import win, get_env_var
|
||||
from ..config import config
|
||||
from .dbconst import DBLOGNAME
|
||||
from .dbconst import DBLOGNAME, DBLOCKFN
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
@@ -179,3 +180,35 @@ def __index_surname(surn_list):
|
||||
else:
|
||||
surn = ""
|
||||
return surn
|
||||
|
||||
def clear_lock_file(name):
|
||||
try:
|
||||
os.unlink(os.path.join(name, DBLOCKFN))
|
||||
except OSError:
|
||||
return
|
||||
|
||||
def write_lock_file(name):
|
||||
if not os.path.isdir(name):
|
||||
os.mkdir(name)
|
||||
with open(os.path.join(name, DBLOCKFN), "w", encoding='utf8') as f:
|
||||
if win():
|
||||
user = get_env_var('USERNAME')
|
||||
host = get_env_var('USERDOMAIN')
|
||||
if host is None:
|
||||
host = ""
|
||||
else:
|
||||
host = os.uname()[1]
|
||||
# An ugly workaround for os.getlogin() issue with Konsole
|
||||
try:
|
||||
user = os.getlogin()
|
||||
except:
|
||||
# not win, so don't need get_env_var.
|
||||
# under cron getlogin() throws and there is no USER.
|
||||
user = os.environ.get('USER', 'noUSER')
|
||||
if host:
|
||||
text = "%s@%s" % (user, host)
|
||||
else:
|
||||
text = user
|
||||
# Save only the username and host, so the massage can be
|
||||
# printed with correct locale in DbManager.py when a lock is found
|
||||
f.write(text)
|
||||
@@ -418,6 +418,11 @@ class NameDisplay:
|
||||
result = raw_data[_FIRSTNAME]
|
||||
return ' '.join(result.split())
|
||||
|
||||
def clear_custom_formats(self):
|
||||
self.name_formats = {num: value
|
||||
for num, value in self.name_formats.items()
|
||||
if num >= 0}
|
||||
|
||||
def set_name_format(self, formats):
|
||||
|
||||
raw_func_dict = {
|
||||
|
||||
+114
-16
@@ -1,7 +1,7 @@
|
||||
#
|
||||
# Gramps - a GTK+/GNOME based genealogy program
|
||||
#
|
||||
# Copyright (C) 2014-2015 Nick Hall
|
||||
# Copyright (C) 2014-2017 Nick Hall
|
||||
#
|
||||
# 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
|
||||
@@ -22,15 +22,43 @@
|
||||
Class handling displaying of places.
|
||||
"""
|
||||
|
||||
#---------------------------------------------------------------
|
||||
#
|
||||
# Python imports
|
||||
#
|
||||
#---------------------------------------------------------------
|
||||
import os
|
||||
import xml.dom.minidom
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Gramps modules
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
from ..const import PLACE_FORMATS
|
||||
from ..config import config
|
||||
from ..utils.location import get_location_list
|
||||
from ..lib import PlaceType
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# PlaceFormat class
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
class PlaceFormat:
|
||||
def __init__(self, name, levels, language, street, reverse):
|
||||
self.name = name
|
||||
self.levels = levels
|
||||
self.language = language
|
||||
self.street = street
|
||||
self.reverse = reverse
|
||||
|
||||
def to_xml(self):
|
||||
return (' <format name="%s" levels="%s" language="%s" '
|
||||
'street="%s" reverse="%s"/>\n' %
|
||||
(self.name, self.levels, self.language,
|
||||
self.street, self.reverse))
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# PlaceDisplay class
|
||||
@@ -38,51 +66,121 @@ from ..lib import PlaceType
|
||||
#-------------------------------------------------------------------------
|
||||
class PlaceDisplay:
|
||||
|
||||
def display_event(self, db, event):
|
||||
def __init__(self):
|
||||
self.place_formats = []
|
||||
self.default_format = config.get('preferences.place-format')
|
||||
if os.path.exists(PLACE_FORMATS):
|
||||
self.load_formats()
|
||||
else:
|
||||
pf = PlaceFormat('Full', ':', '', 0, False)
|
||||
self.place_formats.append(pf)
|
||||
|
||||
def display_event(self, db, event, fmt=None):
|
||||
if not event:
|
||||
return ""
|
||||
place_handle = event.get_place_handle()
|
||||
if place_handle:
|
||||
place = db.get_place_from_handle(place_handle)
|
||||
return self.display(db, place, event.get_date_object())
|
||||
return self.display(db, place, event.get_date_object(), fmt)
|
||||
else:
|
||||
return ""
|
||||
|
||||
def display(self, db, place, date=None):
|
||||
def display(self, db, place, date=None, fmt=None):
|
||||
if not place:
|
||||
return ""
|
||||
if not config.get('preferences.place-auto'):
|
||||
return place.title
|
||||
else:
|
||||
lang = config.get('preferences.place-lang')
|
||||
places = get_location_list(db, place, date, lang)
|
||||
if fmt is None:
|
||||
fmt = config.get('preferences.place-format')
|
||||
pf = self.place_formats[fmt]
|
||||
lang = pf.language
|
||||
all_places = get_location_list(db, place, date, lang)
|
||||
|
||||
if config.get('preferences.place-restrict') > 0:
|
||||
index = _find_populated_place(places)
|
||||
if index is not None:
|
||||
if config.get('preferences.place-restrict') == 1:
|
||||
places = places[:index+1]
|
||||
# Apply format string to place list
|
||||
index = _find_populated_place(all_places)
|
||||
places = []
|
||||
for slice in pf.levels.split(','):
|
||||
parts = slice.split(':')
|
||||
if len(parts) == 1:
|
||||
offset = _get_offset(parts[0], index)
|
||||
if offset is not None:
|
||||
places.append(all_places[offset])
|
||||
elif len(parts) == 2:
|
||||
start = _get_offset(parts[0], index)
|
||||
end = _get_offset(parts[1], index)
|
||||
if start is None:
|
||||
places.extend(all_places[:end])
|
||||
elif end is None:
|
||||
places.extend(all_places[start:])
|
||||
else:
|
||||
places = places[index:]
|
||||
places.extend(all_places[start:end])
|
||||
|
||||
if config.get('preferences.place-number'):
|
||||
if pf.street:
|
||||
types = [item[1] for item in places]
|
||||
try:
|
||||
idx = types.index(PlaceType.NUMBER)
|
||||
except ValueError:
|
||||
idx = None
|
||||
if idx is not None and len(places) > idx+1:
|
||||
combined = (places[idx][0] + ' ' + places[idx+1][0],
|
||||
places[idx+1][1])
|
||||
if pf.street == 1:
|
||||
combined = (places[idx][0] + ' ' + places[idx+1][0],
|
||||
places[idx+1][1])
|
||||
else:
|
||||
combined = (places[idx+1][0] + ' ' + places[idx][0],
|
||||
places[idx+1][1])
|
||||
places = places[:idx] + [combined] + places[idx+2:]
|
||||
|
||||
names = [item[0] for item in places]
|
||||
if config.get('preferences.place-reverse'):
|
||||
if pf.reverse:
|
||||
names.reverse()
|
||||
|
||||
# TODO for Arabic, should the next line's comma be translated?
|
||||
return ", ".join(names)
|
||||
|
||||
def get_formats(self):
|
||||
return self.place_formats
|
||||
|
||||
def set_formats(self, formats):
|
||||
self.place_formats = formats
|
||||
|
||||
def load_formats(self):
|
||||
dom = xml.dom.minidom.parse(PLACE_FORMATS)
|
||||
top = dom.getElementsByTagName('place_formats')
|
||||
|
||||
for fmt in top[0].getElementsByTagName('format'):
|
||||
name = fmt.attributes['name'].value
|
||||
levels = fmt.attributes['levels'].value
|
||||
language = fmt.attributes['language'].value
|
||||
street = int(fmt.attributes['street'].value)
|
||||
reverse = fmt.attributes['reverse'].value == 'True'
|
||||
pf = PlaceFormat(name, levels, language, street, reverse)
|
||||
self.place_formats.append(pf)
|
||||
|
||||
dom.unlink()
|
||||
|
||||
def save_formats(self):
|
||||
with open(PLACE_FORMATS, 'w') as fd:
|
||||
fd.write('<?xml version="1.0" encoding="utf-8"?>\n')
|
||||
fd.write('<place_formats>\n')
|
||||
for fmt in self.place_formats:
|
||||
fd.write(fmt.to_xml())
|
||||
fd.write('</place_formats>\n')
|
||||
|
||||
def _get_offset(value, index):
|
||||
if index is not None and value.startswith('p'):
|
||||
try:
|
||||
offset = int(value[1:])
|
||||
except ValueError:
|
||||
offset = 0
|
||||
offset += index
|
||||
else:
|
||||
try:
|
||||
offset = int(value)
|
||||
except ValueError:
|
||||
offset = None
|
||||
return offset
|
||||
|
||||
def _find_populated_place(places):
|
||||
populated_place = None
|
||||
for index, item in enumerate(places):
|
||||
|
||||
@@ -187,6 +187,7 @@ editor_rule_list = [
|
||||
HasNote,
|
||||
HasNoteRegexp,
|
||||
RegExpIdOf,
|
||||
RegExpName,
|
||||
Disconnected,
|
||||
ChangedSince,
|
||||
IsRelatedWith,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#
|
||||
# Copyright (C) 2009 Robert Ham <rah@bash.sh>
|
||||
# Copyright (C) 2011 Adam Stein <adam@csh.rit.edu>
|
||||
# Copyright (C) 2017 Paul Culley <paulr2787@gmail.com>
|
||||
#
|
||||
# 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
|
||||
@@ -24,8 +25,7 @@
|
||||
# Standard Python modules
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
from ....const import GRAMPS_LOCALE as glocale
|
||||
_ = glocale.translation.gettext
|
||||
from collections import deque
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
@@ -34,13 +34,15 @@ _ = glocale.translation.gettext
|
||||
#-------------------------------------------------------------------------
|
||||
from .. import Rule
|
||||
from . import MatchesFilter
|
||||
|
||||
from ....const import GRAMPS_LOCALE as glocale
|
||||
_ = glocale.translation.gettext
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# DeepRelationshipPathBetween
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
|
||||
def filter_database(db, user, filter_name):
|
||||
"""Returns a list of person handles"""
|
||||
|
||||
@@ -65,13 +67,14 @@ def filter_database(db, user, filter_name):
|
||||
|
||||
return matches
|
||||
|
||||
|
||||
def get_family_handle_people(db, exclude_handle, family_handle):
|
||||
people = set()
|
||||
|
||||
family = db.get_family_from_handle(family_handle)
|
||||
|
||||
def possibly_add_handle(h):
|
||||
if h != None and h != exclude_handle:
|
||||
if h is not None and h != exclude_handle:
|
||||
people.add(h)
|
||||
|
||||
possibly_add_handle(family.get_father_handle())
|
||||
@@ -83,58 +86,84 @@ def get_family_handle_people(db, exclude_handle, family_handle):
|
||||
|
||||
return people
|
||||
|
||||
|
||||
def get_person_family_people(db, person, person_handle):
|
||||
people = set()
|
||||
|
||||
def add_family_handle_list(list):
|
||||
for family_handle in list:
|
||||
people.update(get_family_handle_people(db, person_handle, family_handle))
|
||||
def add_family_handle_list(fam_list):
|
||||
for family_handle in fam_list:
|
||||
people.update(get_family_handle_people(db, person_handle,
|
||||
family_handle))
|
||||
|
||||
add_family_handle_list(person.get_family_handle_list())
|
||||
add_family_handle_list(person.get_parent_family_handle_list())
|
||||
|
||||
return people
|
||||
|
||||
def find_deep_relations(db, user, person, path, seen, target_people):
|
||||
if len(target_people) < 1:
|
||||
return []
|
||||
|
||||
def find_deep_relations(db, user, person, target_people):
|
||||
""" This explores all possible paths between a person and one or more
|
||||
targets. The algorithm processes paths in a breadth first wave, one
|
||||
remove at a time. The first path that reaches a target causes the target
|
||||
to be marked complete and the path to be stored in the return_paths set.
|
||||
By processing in wave, the return path should be a shortest path.
|
||||
The function stores to do data and intermediate results in an ordered dict,
|
||||
rather than using a recursive algorithm becasue some trees have been found
|
||||
that exceed the standard python recursive depth. """
|
||||
return_paths = set() # all people in paths between targets and person
|
||||
if person is None:
|
||||
return []
|
||||
return return_paths
|
||||
todo = deque([person.handle]) # list of work to do, handles, add to right,
|
||||
# pop from left
|
||||
done = {} # The key records handles already examined,
|
||||
# the value is a handle of the previous person in the path, or None at
|
||||
# head of path. This forms a linked list of handles along the path.
|
||||
done[person.handle] = None
|
||||
|
||||
handle = person.get_handle()
|
||||
if handle in seen:
|
||||
return []
|
||||
seen.append(handle)
|
||||
if user:
|
||||
user.step_progress()
|
||||
while todo:
|
||||
handle = todo.popleft()
|
||||
|
||||
return_paths = []
|
||||
person_path = path + [handle]
|
||||
if user:
|
||||
user.step_progress()
|
||||
|
||||
if handle in target_people:
|
||||
return_paths += [person_path]
|
||||
target_people.remove(handle)
|
||||
if handle in target_people: # if we found a target
|
||||
prev_hndl = handle
|
||||
# Go through linked list and save the handles in return_paths
|
||||
while prev_hndl:
|
||||
return_paths.add(prev_hndl)
|
||||
prev_hndl = done[prev_hndl]
|
||||
target_people.remove(handle)
|
||||
if not target_people: # Quit searching if all targets found
|
||||
break
|
||||
|
||||
family_people = get_person_family_people(db, person, handle)
|
||||
for family_person in family_people:
|
||||
pers = db.get_person_from_handle(family_person)
|
||||
return_paths += find_deep_relations(db, user, pers, person_path, seen, target_people)
|
||||
person = db.get_person_from_handle(handle)
|
||||
if person is None:
|
||||
continue
|
||||
|
||||
people = get_person_family_people(db, person, handle)
|
||||
for p_hndl in people:
|
||||
if p_hndl in done: # check if we have already been here
|
||||
continue # and ignore if we have
|
||||
todo.append(p_hndl) # Add to the todo list
|
||||
done[p_hndl] = handle # Add to the (almost) done list
|
||||
|
||||
return return_paths
|
||||
|
||||
|
||||
class DeepRelationshipPathBetween(Rule):
|
||||
"""Checks if there is any familial connection between a person and a
|
||||
filter match by searching over all connections."""
|
||||
|
||||
labels = [ _('ID:'), _('Filter name:') ]
|
||||
name = _("Relationship path between <person> and people matching <filter>")
|
||||
category = _('Relationship filters')
|
||||
description = _("Searches over the database starting from a specified person and"
|
||||
" returns everyone between that person and a set of target people specified"
|
||||
" with a filter. This produces a set of relationship paths (including"
|
||||
" by marriage) between the specified person and the target people."
|
||||
" Each path is not necessarily the shortest path.")
|
||||
labels = [_('ID:'), _('Filter name:')]
|
||||
name = _("Relationship path between <person> and people matching <filter>")
|
||||
category = _('Relationship filters')
|
||||
description = _("Searches over the database starting from a specified"
|
||||
" person and returns everyone between that person and"
|
||||
" a set of target people specified with a filter. "
|
||||
"This produces a set of relationship paths (including"
|
||||
" by marriage) between the specified person and the"
|
||||
" target people. Each path is not necessarily"
|
||||
" the shortest path.")
|
||||
|
||||
def prepare(self, db, user):
|
||||
root_person_id = self.list[0]
|
||||
@@ -147,13 +176,11 @@ class DeepRelationshipPathBetween(Rule):
|
||||
user.begin_progress(_('Finding relationship paths'),
|
||||
_('Evaluating people'),
|
||||
db.get_number_of_people())
|
||||
paths = find_deep_relations(db, user, root_person, [], [], target_people)
|
||||
self.__matches = find_deep_relations(db, user, root_person,
|
||||
target_people)
|
||||
if user:
|
||||
user.end_progress()
|
||||
|
||||
self.__matches = set()
|
||||
list(map(self.__matches.update, paths))
|
||||
|
||||
def reset(self):
|
||||
self.__matches = set()
|
||||
|
||||
|
||||
@@ -82,7 +82,8 @@ class IsDescendantFamilyOf(Rule):
|
||||
|
||||
while expand:
|
||||
person = expand.pop(0)
|
||||
if person is None:
|
||||
if person is None or person.handle in self.matches:
|
||||
# if we have been here before, skip
|
||||
continue
|
||||
self.matches.add(person.handle)
|
||||
for family_handle in person.get_family_handle_list():
|
||||
|
||||
@@ -67,7 +67,8 @@ class IsDescendantOf(Rule):
|
||||
return person.handle in self.map
|
||||
|
||||
def init_list(self, person, first):
|
||||
if not person:
|
||||
if not person or person.handle in self.map:
|
||||
# if we have been here before, skip
|
||||
return
|
||||
if not first:
|
||||
self.map.add(person.handle)
|
||||
|
||||
@@ -61,6 +61,9 @@ class IsLessThanNthGenerationAncestorOf(Rule):
|
||||
queue = [(root_handle, 1)] # generation 1 is root
|
||||
while queue:
|
||||
handle, gen = queue.pop(0) # pop off front of queue
|
||||
if handle in self.map:
|
||||
# if we have been here before, skip
|
||||
continue
|
||||
self.map.add(handle)
|
||||
gen += 1
|
||||
if gen <= int(self.list[1]):
|
||||
|
||||
@@ -71,7 +71,8 @@ class IsLessThanNthGenerationAncestorOfBookmarked(Rule):
|
||||
def init_ancestor_list(self, handle, gen):
|
||||
# if p.get_handle() in self.map:
|
||||
# loop_error(self.orig,p)
|
||||
if not handle:
|
||||
if not handle or handle in self.map:
|
||||
# if been here already, skip
|
||||
return
|
||||
if gen:
|
||||
self.map.add(handle)
|
||||
@@ -80,6 +81,8 @@ class IsLessThanNthGenerationAncestorOfBookmarked(Rule):
|
||||
|
||||
p = self.db.get_person_from_handle(handle)
|
||||
fam_id = p.get_main_parents_family_handle()
|
||||
if not fam_id:
|
||||
return
|
||||
fam = self.db.get_family_from_handle(fam_id)
|
||||
if fam:
|
||||
f_id = fam.get_father_handle()
|
||||
|
||||
@@ -64,7 +64,8 @@ class IsLessThanNthGenerationAncestorOfDefaultPerson(Rule):
|
||||
def init_ancestor_list(self, handle, gen):
|
||||
# if p.get_handle() in self.map:
|
||||
# loop_error(self.orig,p)
|
||||
if not handle:
|
||||
if not handle or handle in self.map:
|
||||
# if we have been here before, skip
|
||||
return
|
||||
if gen:
|
||||
self.map.add(handle)
|
||||
@@ -73,6 +74,8 @@ class IsLessThanNthGenerationAncestorOfDefaultPerson(Rule):
|
||||
|
||||
p = self.db.get_person_from_handle(handle)
|
||||
fam_id = p.get_main_parents_family_handle()
|
||||
if not fam_id:
|
||||
return
|
||||
fam = self.db.get_family_from_handle(fam_id)
|
||||
if fam:
|
||||
f_id = fam.get_father_handle()
|
||||
|
||||
@@ -65,7 +65,8 @@ class IsLessThanNthGenerationDescendantOf(Rule):
|
||||
return person.handle in self.map
|
||||
|
||||
def init_list(self,person,gen):
|
||||
if not person:
|
||||
if not person or person.handle in self.map:
|
||||
# if we have been here before, skip
|
||||
return
|
||||
if gen:
|
||||
self.map.add(person.handle)
|
||||
|
||||
@@ -17,26 +17,43 @@
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
#
|
||||
from build.lib.gramps.gen.filters.rules.person._matchidof import MatchIdOf
|
||||
|
||||
"""
|
||||
Unittest that tests person-specific filter rules
|
||||
"""
|
||||
import unittest
|
||||
import os
|
||||
import time
|
||||
import inspect
|
||||
|
||||
from ....db.utils import import_as_dict
|
||||
from ....filters import GenericFilter
|
||||
from ....filters import GenericFilter, CustomFilters
|
||||
from ....const import DATA_DIR
|
||||
from ....user import User
|
||||
|
||||
from ..person import (
|
||||
Disconnected, Everyone, FamilyWithIncompleteEvent, HasAlternateName,
|
||||
HasCommonAncestorWith, HasNickname, HasUnknownGender, HasSourceOf,
|
||||
HaveAltFamilies, HaveChildren, IncompleteNames, IsBookmarked,
|
||||
IsDuplicatedAncestorOf, IsRelatedWith, HasIdOf, IsDefaultPerson, IsFemale,
|
||||
IsMale, MissingParent, MultipleMarriages, NeverMarried, NoBirthdate,
|
||||
NoDeathdate, PeoplePrivate, PeoplePublic, PersonWithIncompleteEvent,
|
||||
RelationshipPathBetweenBookmarks, HasNameOf, HasSoundexName)
|
||||
Disconnected, Everyone, FamilyWithIncompleteEvent, HasAddress,
|
||||
HasAlternateName, HasAssociation, HasBirth, HasDeath, HasEvent,
|
||||
HasCommonAncestorWith, HasCommonAncestorWithFilterMatch,
|
||||
HasFamilyAttribute, HasFamilyEvent, HasIdOf, HasLDS,
|
||||
HasNameOf, HasNameOriginType, HasNameType, HasNickname, HasRelationship,
|
||||
HasSoundexName, HasSourceOf, HasTextMatchingRegexpOf, HasUnknownGender,
|
||||
HaveAltFamilies, HaveChildren, HavePhotos, IncompleteNames,
|
||||
IsAncestorOfFilterMatch, IsBookmarked, IsChildOfFilterMatch,
|
||||
IsDescendantFamilyOf, IsDescendantFamilyOfFilterMatch,
|
||||
IsDescendantOfFilterMatch, IsDefaultPerson, IsDescendantOf,
|
||||
IsDuplicatedAncestorOf, IsFemale,
|
||||
IsLessThanNthGenerationAncestorOf,
|
||||
IsLessThanNthGenerationAncestorOfDefaultPerson,
|
||||
IsLessThanNthGenerationAncestorOfBookmarked, IsMale,
|
||||
IsMoreThanNthGenerationAncestorOf, IsMoreThanNthGenerationDescendantOf,
|
||||
IsParentOfFilterMatch, IsRelatedWith, IsSiblingOfFilterMatch,
|
||||
IsSpouseOfFilterMatch, IsWitness, MissingParent, MultipleMarriages,
|
||||
NeverMarried, NoBirthdate, NoDeathdate, PeoplePrivate, PeoplePublic,
|
||||
PersonWithIncompleteEvent, ProbablyAlive, RegExpName,
|
||||
RelationshipPathBetweenBookmarks,
|
||||
)
|
||||
|
||||
TEST_DIR = os.path.abspath(os.path.join(DATA_DIR, "tests"))
|
||||
EXAMPLE = os.path.join(TEST_DIR, "example.gramps")
|
||||
@@ -53,15 +70,292 @@ class BaseTest(unittest.TestCase):
|
||||
"""
|
||||
cls.db = import_as_dict(EXAMPLE, User())
|
||||
|
||||
def filter_with_rule(self, rule):
|
||||
def filter_with_rule(self, rule, l_op='and', invert=False,
|
||||
baserule=None, base_l_op='and', base_invert=False,
|
||||
base_name='Base'):
|
||||
"""
|
||||
Apply a filter with the given rule.
|
||||
Apply a filter with the given rule. 'baserule' can be used to stack
|
||||
filters when using filters that are of 'offiltermatch' type.
|
||||
"""
|
||||
if baserule:
|
||||
filter_ = GenericFilter()
|
||||
if isinstance(baserule, list):
|
||||
filter_.set_rules(baserule)
|
||||
else:
|
||||
filter_.add_rule(baserule)
|
||||
filter_.set_logical_op(base_l_op)
|
||||
filter_.set_invert(base_invert)
|
||||
filter_.set_name(base_name)
|
||||
filters = CustomFilters.get_filters_dict('Person')
|
||||
filters[base_name] = filter_
|
||||
filter_ = GenericFilter()
|
||||
filter_.add_rule(rule)
|
||||
if isinstance(rule, list):
|
||||
filter_.set_rules(rule)
|
||||
else:
|
||||
filter_.add_rule(rule)
|
||||
filter_.set_logical_op(l_op)
|
||||
filter_.set_invert(invert)
|
||||
stime = time.clock()
|
||||
results = filter_.apply(self.db)
|
||||
if __debug__:
|
||||
rulename = inspect.stack()[1][3]
|
||||
print("%s: %.2f\n" % (rulename, time.clock() - stime))
|
||||
return set(results)
|
||||
|
||||
def test_Complex_1(self):
|
||||
""" Test with two ancestor trees in base filter, and a complex
|
||||
'or' of a descendent tree, sibling of, """
|
||||
rule1 = IsLessThanNthGenerationAncestorOf(['I0005', 10])
|
||||
rule2 = IsLessThanNthGenerationAncestorOf(['I0006', 10])
|
||||
ruleA = IsDescendantOfFilterMatch(['Base'])
|
||||
ruleB = IsSiblingOfFilterMatch(['Base'])
|
||||
ruleC = IsSpouseOfFilterMatch(['Base'])
|
||||
res = self.filter_with_rule([ruleA, ruleB, ruleC], l_op='or',
|
||||
baserule=[rule1, rule2], base_l_op='or')
|
||||
self.assertEqual(len(res), 1277)
|
||||
|
||||
def test_IsDescendantOfFilterMatch(self):
|
||||
""" Test the rule with two persons in base filter """
|
||||
rule1 = HasIdOf(['I0610'])
|
||||
rule2 = HasIdOf(['I1804'])
|
||||
rule = IsDescendantOfFilterMatch(['Base'])
|
||||
res = self.filter_with_rule(rule, baserule=[rule1, rule2],
|
||||
base_l_op='or')
|
||||
self.assertEqual(len(res), 120)
|
||||
|
||||
def test_IsDescendantFamilyOfFilterMatch(self):
|
||||
""" Test the rule with two persons in base filter """
|
||||
rule1 = HasIdOf(['I0610'])
|
||||
rule2 = HasIdOf(['I1804'])
|
||||
rule = IsDescendantFamilyOfFilterMatch(['Base'])
|
||||
res = self.filter_with_rule(rule, baserule=[rule1, rule2],
|
||||
base_l_op='or')
|
||||
self.assertEqual(len(res), 171)
|
||||
|
||||
def test_IsDescendantFamilyOf(self):
|
||||
""" Test the rule """
|
||||
rule = IsDescendantFamilyOf(['I0610', 1])
|
||||
res = self.filter_with_rule(rule)
|
||||
self.assertEqual(len(res), 118)
|
||||
|
||||
def test_IsDescendantOf(self):
|
||||
""" Test the rule """
|
||||
rule = IsDescendantOf(['I0610', 0])
|
||||
res = self.filter_with_rule(rule)
|
||||
self.assertEqual(len(res), 85)
|
||||
|
||||
def test_IsMoreThanNthGenerationDescendantOf(self):
|
||||
""" Test the rule """
|
||||
rule = IsMoreThanNthGenerationDescendantOf(['I0610', 3])
|
||||
res = self.filter_with_rule(rule)
|
||||
self.assertEqual(len(res), 83)
|
||||
|
||||
def test_IsLessThanNthGenerationAncestorOfDefaultPerson(self):
|
||||
""" Test the rule """
|
||||
rule = IsLessThanNthGenerationAncestorOfDefaultPerson([5])
|
||||
res = self.filter_with_rule(rule)
|
||||
self.assertEqual(len(res), 7)
|
||||
|
||||
def test_IsLessThanNthGenerationAncestorOfBookmarked(self):
|
||||
""" Test the rule """
|
||||
rule = IsLessThanNthGenerationAncestorOfBookmarked([5])
|
||||
res = self.filter_with_rule(rule)
|
||||
self.assertEqual(len(res), 8)
|
||||
|
||||
def test_IsParentOfFilterMatch(self):
|
||||
""" Test the rule with two persons in base filter """
|
||||
rule1 = HasIdOf(['I0006'])
|
||||
rule2 = HasIdOf(['I0010'])
|
||||
rule = IsParentOfFilterMatch(['Base'])
|
||||
res = self.filter_with_rule(rule, baserule=[rule1, rule2],
|
||||
base_l_op='or')
|
||||
self.assertEqual(len(res), 4)
|
||||
|
||||
def test_IsSiblingOfFilterMatch(self):
|
||||
""" Test the rule with two persons in base filter """
|
||||
rule1 = HasIdOf(['I0006'])
|
||||
rule2 = HasIdOf(['I0010'])
|
||||
rule = IsSiblingOfFilterMatch(['Base'])
|
||||
res = self.filter_with_rule(rule, baserule=[rule1, rule2],
|
||||
base_l_op='or')
|
||||
self.assertEqual(len(res), 7)
|
||||
|
||||
def test_IsSpouseOfFilterMatch(self):
|
||||
""" Test the rule with two persons in base filter """
|
||||
rule1 = HasIdOf(['I0006'])
|
||||
rule2 = HasIdOf(['I0010'])
|
||||
rule = IsSpouseOfFilterMatch(['Base'])
|
||||
res = self.filter_with_rule(rule, baserule=[rule1, rule2],
|
||||
base_l_op='or')
|
||||
self.assertEqual(len(res), 2)
|
||||
|
||||
def test_isancestoroffiltermatch(self):
|
||||
""" Test the rule with two persons in base filter """
|
||||
rule1 = HasIdOf(['I0006'])
|
||||
rule2 = HasIdOf(['I0005'])
|
||||
rule = IsAncestorOfFilterMatch(['Base'])
|
||||
res = self.filter_with_rule(rule, baserule=[rule1, rule2],
|
||||
base_l_op='or')
|
||||
self.assertEqual(len(res), 431)
|
||||
|
||||
def test_HasCommonAncestorWithFilterMatch(self):
|
||||
""" Test the rule with two persons in base filter """
|
||||
rule1 = HasIdOf(['I0006'])
|
||||
rule2 = HasIdOf(['I0005'])
|
||||
rule = HasCommonAncestorWithFilterMatch(['Base'])
|
||||
res = self.filter_with_rule(rule, baserule=[rule1, rule2],
|
||||
base_l_op='or')
|
||||
self.assertEqual(len(res), 1415)
|
||||
|
||||
def test_IsChildOfFilterMatch(self):
|
||||
""" Test the rule with two persons in base filter """
|
||||
rule1 = HasIdOf(['I0006'])
|
||||
rule2 = HasIdOf(['I0010'])
|
||||
rule = IsChildOfFilterMatch(['Base'])
|
||||
res = self.filter_with_rule(rule, baserule=[rule1, rule2],
|
||||
base_l_op='or')
|
||||
self.assertEqual(len(res), 11)
|
||||
|
||||
def test_HasAddress(self):
|
||||
"""
|
||||
Test HasAddress rule.
|
||||
"""
|
||||
rule = HasAddress([0, 'greater than'])
|
||||
res = self.filter_with_rule(rule)
|
||||
self.assertEqual(len(res), 1)
|
||||
|
||||
def test_HasAssociation(self):
|
||||
"""
|
||||
Test rule.
|
||||
"""
|
||||
rule = HasAssociation([0, 'greater than'])
|
||||
res = self.filter_with_rule(rule)
|
||||
self.assertEqual(len(res), 2)
|
||||
|
||||
def test_HasBirth(self):
|
||||
"""
|
||||
Test rule.
|
||||
"""
|
||||
rule = HasBirth(['between 1600 and 1700', 'akron', ''])
|
||||
res = self.filter_with_rule(rule)
|
||||
self.assertEqual(len(res), 2)
|
||||
|
||||
def test_HasDeath(self):
|
||||
"""
|
||||
Test HasDeath rule.
|
||||
"""
|
||||
rule = HasDeath(['between 1600 and 1700', 'ashtabula', ''])
|
||||
res = self.filter_with_rule(rule)
|
||||
self.assertEqual(len(res), 2)
|
||||
|
||||
def test_HasEvent(self):
|
||||
"""
|
||||
Test rule.
|
||||
"""
|
||||
rule = HasEvent(['Birth', 'between 1600 and 1700', 'akron',
|
||||
'', '', 1])
|
||||
res = self.filter_with_rule(rule)
|
||||
self.assertEqual(len(res), 2)
|
||||
|
||||
def test_HasFamilyAttribute(self):
|
||||
"""
|
||||
Test rule.
|
||||
"""
|
||||
rule = HasFamilyAttribute([5, '8'])
|
||||
res = self.filter_with_rule(rule)
|
||||
self.assertEqual(len(res), 2)
|
||||
|
||||
def test_HasFamilyEvent(self):
|
||||
"""
|
||||
Test rule.
|
||||
"""
|
||||
rule = HasFamilyEvent(['Marriage', 'after 1900', 'craw', ''])
|
||||
res = self.filter_with_rule(rule)
|
||||
self.assertEqual(len(res), 4)
|
||||
|
||||
def test_HavePhotos(self):
|
||||
"""
|
||||
Test rule.
|
||||
"""
|
||||
rule = HavePhotos([0, 'greater than'])
|
||||
res = self.filter_with_rule(rule)
|
||||
self.assertEqual(len(res), 5)
|
||||
|
||||
def test_HasLDS(self):
|
||||
"""
|
||||
Test rule.
|
||||
"""
|
||||
rule = HasLDS([0, 'greater than'])
|
||||
res = self.filter_with_rule(rule)
|
||||
self.assertEqual(len(res), 1)
|
||||
|
||||
def test_HasNameOriginType(self):
|
||||
"""
|
||||
Test rule.
|
||||
"""
|
||||
rule = HasNameOriginType(['Patrilineal'])
|
||||
res = self.filter_with_rule(rule)
|
||||
self.assertEqual(len(res), 9)
|
||||
|
||||
def test_HasNameType(self):
|
||||
"""
|
||||
Test rule.
|
||||
"""
|
||||
rule = HasNameType(['Married Name'])
|
||||
res = self.filter_with_rule(rule)
|
||||
self.assertEqual(len(res), 1)
|
||||
|
||||
def test_HasRelationship(self):
|
||||
"""
|
||||
Test rule.
|
||||
"""
|
||||
rule = HasRelationship([0, 'Married', 0])
|
||||
res = self.filter_with_rule(rule)
|
||||
self.assertEqual(len(res), 1377)
|
||||
|
||||
def test_HasTextMatchingRegexpOf(self):
|
||||
"""
|
||||
Test rule.
|
||||
"""
|
||||
rule = HasTextMatchingRegexpOf(['.*(Dahl|Akron|Smith|Attic|'
|
||||
'of Lessard).*', False],
|
||||
use_regex=True)
|
||||
res = self.filter_with_rule(rule)
|
||||
self.assertEqual(len(res), 28)
|
||||
|
||||
def test_IsMoreThanNthGenerationAncestorOf(self):
|
||||
"""
|
||||
Test rule.
|
||||
"""
|
||||
rule = IsMoreThanNthGenerationAncestorOf(['I0005', 3])
|
||||
res = self.filter_with_rule(rule)
|
||||
self.assertEqual(len(res), 322)
|
||||
|
||||
def test_IsWitness(self):
|
||||
"""
|
||||
Test rule.
|
||||
"""
|
||||
rule = IsWitness(['Marriage'])
|
||||
res = self.filter_with_rule(rule)
|
||||
self.assertEqual(len(res), 1)
|
||||
|
||||
def test_ProbablyAlive(self):
|
||||
"""
|
||||
Test rule.
|
||||
"""
|
||||
rule = ProbablyAlive(['1900'])
|
||||
res = self.filter_with_rule(rule)
|
||||
self.assertEqual(len(res), 766)
|
||||
|
||||
def test_RegExpName(self):
|
||||
"""
|
||||
Test rule.
|
||||
"""
|
||||
rule = RegExpName(['.*(Garc|Amy).*'], use_regex=True)
|
||||
res = self.filter_with_rule(rule)
|
||||
self.assertEqual(len(res), 3)
|
||||
|
||||
|
||||
def test_disconnected(self):
|
||||
"""
|
||||
Test Disconnected rule.
|
||||
|
||||
@@ -44,11 +44,11 @@ class FamilyRelType(GrampsType):
|
||||
_DEFAULT = MARRIED
|
||||
|
||||
_DATAMAP = [
|
||||
(MARRIED, _("Married"), "Married"),
|
||||
(UNMARRIED, _("Unmarried"), "Unmarried"),
|
||||
(CIVIL_UNION, _("Civil Union"), "Civil Union"),
|
||||
(UNKNOWN, _("Unknown"), "Unknown"),
|
||||
(CUSTOM, _("Custom"), "Custom"),
|
||||
(CIVIL_UNION, _("Civil Union"), "Civil Union"),
|
||||
(UNMARRIED, _("Unmarried"), "Unmarried"),
|
||||
(MARRIED, _("Married"), "Married"),
|
||||
]
|
||||
|
||||
def __init__(self, value=None):
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
# Gramps modules
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
from copy import copy
|
||||
from .styledtexttag import StyledTextTag
|
||||
from ..const import GRAMPS_LOCALE as glocale
|
||||
_ = glocale.translation.gettext
|
||||
@@ -77,6 +78,8 @@ class StyledText:
|
||||
There could be a 'merge_tags' functionality in :py:meth:`__init__`,
|
||||
however :py:class:`StyledTextBuffer` will merge them automatically if
|
||||
the text is displayed.
|
||||
3. Warning: Some of these operations modify the source tag ranges in place
|
||||
so if you intend to use a source tag more than once, copy it for use.
|
||||
"""
|
||||
(POS_TEXT, POS_TAGS) = list(range(2))
|
||||
|
||||
@@ -198,17 +201,27 @@ class StyledText:
|
||||
new_string = self._string.join([str(string) for string in seq])
|
||||
|
||||
offset = 0
|
||||
not_first = False
|
||||
new_tags = []
|
||||
self_len = len(self._string)
|
||||
|
||||
for text in seq:
|
||||
if not_first: # if not first time through...
|
||||
# put the joined element tag(s) into place
|
||||
for tag in self.tags:
|
||||
ntag = copy(tag)
|
||||
ntag.ranges = [(start + offset, end + offset)
|
||||
for (start, end) in tag.ranges]
|
||||
new_tags += [ntag]
|
||||
offset += self_len
|
||||
if isinstance(text, StyledText):
|
||||
for tag in text.tags:
|
||||
tag.ranges = [(start + offset, end + offset)
|
||||
for (start, end) in tag.ranges]
|
||||
new_tags += [tag]
|
||||
|
||||
offset = offset + len(str(text)) + self_len
|
||||
ntag = copy(tag)
|
||||
ntag.ranges = [(start + offset, end + offset)
|
||||
for (start, end) in tag.ranges]
|
||||
new_tags += [ntag]
|
||||
offset += len(str(text))
|
||||
not_first = True
|
||||
|
||||
return self.__class__(new_string, new_tags)
|
||||
|
||||
@@ -366,6 +379,7 @@ if __name__ == '__main__':
|
||||
from .styledtexttagtype import StyledTextTagType
|
||||
T1 = StyledTextTag(StyledTextTagType(1), 'v1', [(0, 2), (2, 4), (4, 6)])
|
||||
T2 = StyledTextTag(StyledTextTagType(2), 'v2', [(1, 3), (3, 5), (0, 7)])
|
||||
T3 = StyledTextTag(StyledTextTagType(0), 'v3', [(0, 1)])
|
||||
|
||||
A = StyledText('123X456', [T1])
|
||||
B = StyledText("abcXdef", [T2])
|
||||
@@ -376,7 +390,7 @@ if __name__ == '__main__':
|
||||
|
||||
C = C.join([A, S, B])
|
||||
L = C.split()
|
||||
C = C.replace('X', StyledText('_'))
|
||||
C = C.replace('X', StyledText('_', [T3]))
|
||||
A = A + B
|
||||
|
||||
print(A)
|
||||
@@ -114,9 +114,10 @@ def generate_case(obj):
|
||||
#setattr(DatabaseCheck, name, test2)
|
||||
|
||||
db = import_as_dict(EXAMPLE, User())
|
||||
for table in db.get_table_names():
|
||||
for handle in db.get_table_metadata(table)["handles_func"]():
|
||||
obj = db.get_table_metadata(table)["handle_func"](handle)
|
||||
for obj_class in ('Person', 'Family', 'Event', 'Place', 'Repository', 'Source',
|
||||
'Citation', 'Media', 'Note'):
|
||||
for handle in db.method('get_%s_handles', obj_class)():
|
||||
obj = db.method('get_%s_from_handle', obj_class)(handle)
|
||||
generate_case(obj)
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
#
|
||||
# Gramps - a GTK+/GNOME based genealogy program
|
||||
#
|
||||
# Copyright (C) 2017 Paul Culley
|
||||
#
|
||||
# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
#
|
||||
|
||||
""" unittest for styledtext """
|
||||
|
||||
import unittest
|
||||
from copy import deepcopy
|
||||
from ..styledtext import StyledText
|
||||
from ..styledtexttag import StyledTextTag
|
||||
from ..styledtexttagtype import StyledTextTagType
|
||||
|
||||
|
||||
class Test1(unittest.TestCase):
|
||||
T1 = StyledTextTag(StyledTextTagType(1), 'v1', [(0, 2), (2, 4), (4, 6)])
|
||||
T2 = StyledTextTag(StyledTextTagType(2), 'v2', [(1, 3), (3, 5), (0, 7)])
|
||||
T3 = StyledTextTag(StyledTextTagType(0), 'v3', [(0, 1)])
|
||||
T4 = StyledTextTag(StyledTextTagType(2), 'v2',
|
||||
[(8, 10), (10, 12), (7, 14)])
|
||||
T5 = StyledTextTag(StyledTextTagType(2), 'v2',
|
||||
[(19, 21), (21, 23), (18, 25)])
|
||||
|
||||
A = StyledText('123X456', [T1])
|
||||
B = StyledText("abcXdef", [T2])
|
||||
|
||||
C = StyledText('\n')
|
||||
|
||||
S = 'cleartext'
|
||||
|
||||
# some basic tests
|
||||
# because the StyledText.__eq__ method doesn't work very well (tags don't
|
||||
# compare when they are equivalent, but not equal) we have to use
|
||||
# serialize for comparisons.
|
||||
def test_join(self):
|
||||
C = self.C.join([self.A, self.S, deepcopy(self.B)])
|
||||
_C = StyledText('123X456\ncleartext\nabcXdef', [self.T1, self.T5])
|
||||
self.assertEqual(C.serialize(), _C.serialize())
|
||||
|
||||
def test_split(self):
|
||||
C = self.C.join([self.A, self.S, deepcopy(self.B)])
|
||||
L = C.split()
|
||||
_L = [self.A, self.S, self.B]
|
||||
self.assertEqual(L[0].serialize(), self.A.serialize())
|
||||
self.assertEqual(str(L[1]), self.S)
|
||||
self.assertEqual(L[2].serialize(), self.B.serialize())
|
||||
|
||||
def test_replace(self):
|
||||
C = self.C.join([self.A, self.S, deepcopy(self.B)])
|
||||
C = C.replace('X', StyledText('_', [self.T3]))
|
||||
_C = ('123_456\ncleartext\nabc_def',
|
||||
[((1, ''), 'v1', [(0, 2), (2, 3)]),
|
||||
((0, ''), 'v3', [(3, 4)]),
|
||||
((1, ''), 'v1', [(4, 6)]),
|
||||
((2, ''), 'v2', [(19, 21), (18, 21)]),
|
||||
((0, ''), 'v3', [(21, 22)]),
|
||||
((2, ''), 'v2', [(22, 23), (22, 25)])])
|
||||
self.assertEqual(C.serialize(), _C)
|
||||
|
||||
def test_add(self):
|
||||
A = deepcopy(self.A) + deepcopy(self.B)
|
||||
_A = StyledText('123X456abcXdef', [self.T1, self.T4])
|
||||
self.assertEqual(A.serialize(), _A.serialize())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -109,10 +109,10 @@ def diff_dbs(db1, db2, user):
|
||||
'Place', 'Repository', 'Note', 'Tag']:
|
||||
step()
|
||||
|
||||
handles_func1 = db1.get_table_metadata(item)["handles_func"]
|
||||
handles_func2 = db2.get_table_metadata(item)["handles_func"]
|
||||
handle_func1 = db1.get_table_metadata(item)["handle_func"]
|
||||
handle_func2 = db2.get_table_metadata(item)["handle_func"]
|
||||
handles_func1 = db1.method('get_%s_handles', item)
|
||||
handles_func2 = db2.method('get_%s_handles', item)
|
||||
handle_func1 = db1.method('get_%s_from_handle', item)
|
||||
handle_func2 = db2.method('get_%s_from_handle', item)
|
||||
|
||||
handles1 = sorted([handle for handle in handles_func1()])
|
||||
handles2 = sorted([handle for handle in handles_func2()])
|
||||
|
||||
+12
-12
@@ -27,7 +27,7 @@ The "plug" package for handling plugins in Gramps.
|
||||
from ._plugin import Plugin
|
||||
from ._pluginreg import (PluginData, PluginRegister, REPORT, TOOL,
|
||||
CATEGORY_TEXT, CATEGORY_DRAW, CATEGORY_CODE,
|
||||
CATEGORY_WEB, CATEGORY_BOOK, CATEGORY_GRAPHVIZ,
|
||||
CATEGORY_WEB, CATEGORY_BOOK, CATEGORY_GRAPHVIZ, CATEGORY_TREE,
|
||||
TOOL_DEBUG, TOOL_ANAL, TOOL_DBPROC, TOOL_DBFIX, TOOL_REVCTL,
|
||||
TOOL_UTILS, CATEGORY_QR_MISC, CATEGORY_QR_PERSON,
|
||||
CATEGORY_QR_FAMILY, CATEGORY_QR_EVENT, CATEGORY_QR_SOURCE,
|
||||
@@ -46,14 +46,14 @@ from ._options import (Options, OptionListCollection, OptionList,
|
||||
OptionHandler, MenuOptions)
|
||||
|
||||
__all__ = [ "docbackend", "docgen", "menu", "Plugin", "PluginData",
|
||||
"PluginRegister", "BasePluginManager",
|
||||
"ImportPlugin", "ExportPlugin", "DocGenPlugin",
|
||||
"REPORT", "TOOL", "CATEGORY_TEXT", "CATEGORY_DRAW", "CATEGORY_CODE",
|
||||
"CATEGORY_WEB", "CATEGORY_BOOK", "CATEGORY_GRAPHVIZ",
|
||||
"TOOL_DEBUG", "TOOL_ANAL", "TOOL_DBPROC", "TOOL_DBFIX", "TOOL_REVCTL",
|
||||
"TOOL_UTILS", "CATEGORY_QR_MISC", "CATEGORY_QR_PERSON",
|
||||
"CATEGORY_QR_FAMILY", "CATEGORY_QR_EVENT", "CATEGORY_QR_SOURCE",
|
||||
"CATEGORY_QR_PLACE", "CATEGORY_QR_REPOSITORY", "CATEGORY_QR_NOTE",
|
||||
"CATEGORY_QR_DATE", "PTYPE_STR", "CATEGORY_QR_MEDIA",
|
||||
"CATEGORY_QR_CITATION", "CATEGORY_QR_SOURCE_OR_CITATION",
|
||||
"START", "END", "make_environment"]
|
||||
"PluginRegister", "BasePluginManager", "ImportPlugin",
|
||||
"ExportPlugin", "DocGenPlugin", "REPORT", "TOOL", "CATEGORY_TEXT",
|
||||
"CATEGORY_DRAW", "CATEGORY_CODE", "CATEGORY_WEB", "CATEGORY_BOOK",
|
||||
"CATEGORY_GRAPHVIZ", "CATEGORY_TREE", "TOOL_DEBUG", "TOOL_ANAL",
|
||||
"TOOL_DBPROC", "TOOL_DBFIX", "TOOL_REVCTL","TOOL_UTILS",
|
||||
"CATEGORY_QR_MISC", "CATEGORY_QR_PERSON", "CATEGORY_QR_FAMILY",
|
||||
"CATEGORY_QR_EVENT", "CATEGORY_QR_SOURCE", "CATEGORY_QR_PLACE",
|
||||
"CATEGORY_QR_REPOSITORY", "CATEGORY_QR_NOTE", "CATEGORY_QR_DATE",
|
||||
"PTYPE_STR", "CATEGORY_QR_MEDIA", "CATEGORY_QR_CITATION",
|
||||
"CATEGORY_QR_SOURCE_OR_CITATION", "START", "END",
|
||||
"make_environment"]
|
||||
@@ -133,11 +133,12 @@ class Gramplet:
|
||||
def get_active_object(self, nav_type):
|
||||
"""
|
||||
Return the object of the active handle for the given navigation type.
|
||||
Assumes nav_type is one of the codes of Db.get_by_name.
|
||||
"""
|
||||
handle = self.uistate.get_active(nav_type, self.nav_group)
|
||||
if nav_type in self.dbstate.db.get_table_names() and handle:
|
||||
return self.dbstate.db.get_from_name_and_handle(nav_type, handle)
|
||||
handle_func = getattr(self.dbstate.db,
|
||||
'get_%s_from_handle' % nav_type.lower())
|
||||
if handle:
|
||||
return handle_func(handle)
|
||||
return None
|
||||
|
||||
def set_active(self, nav_type, handle):
|
||||
|
||||
@@ -97,8 +97,10 @@ CATEGORY_CODE = 2
|
||||
CATEGORY_WEB = 3
|
||||
CATEGORY_BOOK = 4
|
||||
CATEGORY_GRAPHVIZ = 5
|
||||
CATEGORY_TREE = 6
|
||||
REPORT_CAT = [ CATEGORY_TEXT, CATEGORY_DRAW, CATEGORY_CODE,
|
||||
CATEGORY_WEB, CATEGORY_BOOK, CATEGORY_GRAPHVIZ]
|
||||
CATEGORY_WEB, CATEGORY_BOOK, CATEGORY_GRAPHVIZ,
|
||||
CATEGORY_TREE]
|
||||
#possible tool categories
|
||||
TOOL_DEBUG = -1
|
||||
TOOL_ANAL = 0
|
||||
@@ -1043,6 +1045,7 @@ def make_environment(**kwargs):
|
||||
'CATEGORY_WEB': CATEGORY_WEB,
|
||||
'CATEGORY_BOOK': CATEGORY_BOOK,
|
||||
'CATEGORY_GRAPHVIZ': CATEGORY_GRAPHVIZ,
|
||||
'CATEGORY_TREE': CATEGORY_TREE,
|
||||
'TOOL_DEBUG': TOOL_DEBUG,
|
||||
'TOOL_ANAL': TOOL_ANAL,
|
||||
'TOOL_DBPROC': TOOL_DBPROC,
|
||||
@@ -1242,8 +1245,7 @@ class PluginRegister:
|
||||
"""
|
||||
Return a list of :class:`PluginData` that are of type ptype
|
||||
"""
|
||||
return [self.get_plugin(id) for id in
|
||||
set([x.id for x in self.__plugindata if x.ptype == ptype])]
|
||||
return [x for x in self.__plugindata if x.ptype == ptype]
|
||||
|
||||
def report_plugins(self, gui=True):
|
||||
"""
|
||||
@@ -1352,6 +1354,4 @@ class PluginRegister:
|
||||
"""
|
||||
Return a list of :class:`PluginData` that have load_on_reg == True
|
||||
"""
|
||||
return [self.get_plugin(id) for id in
|
||||
set([x.id for x in self.__plugindata
|
||||
if x.load_on_reg == True])]
|
||||
return [x for x in self.__plugindata if x.load_on_reg == True]
|
||||
@@ -37,3 +37,4 @@ from .textdoc import TextDoc, IndexMark,INDEX_TYPE_ALP, INDEX_TYPE_TOC,\
|
||||
URL_PATTERN, LOCAL_HYPERLINK, LOCAL_TARGET
|
||||
from .drawdoc import DrawDoc
|
||||
from .graphdoc import GVDoc
|
||||
from .treedoc import TreeDoc
|
||||
+294
-243
@@ -8,6 +8,8 @@
|
||||
# Copyright (C) 2007 Brian G. Matherly
|
||||
# Copyright (C) 2009 Benny Malengier
|
||||
# Copyright (C) 2009 Gary Burton
|
||||
# Copyright (C) 2017 Mindaugas Baranauskas
|
||||
# Copyright (C) 2017 Paul Culley
|
||||
#
|
||||
# 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
|
||||
@@ -23,7 +25,7 @@
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
#
|
||||
|
||||
""" Graphviz adapter for Graphs """
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Standard Python modules
|
||||
@@ -34,19 +36,18 @@ import os
|
||||
from io import BytesIO
|
||||
import tempfile
|
||||
from subprocess import Popen, PIPE
|
||||
import sys
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Gramps modules
|
||||
#
|
||||
#-------------------------------------------------------------------------------
|
||||
#-------------------------------------------------------------------------
|
||||
from ...const import GRAMPS_LOCALE as glocale
|
||||
_ = glocale.translation.gettext
|
||||
from ...utils.file import search_for
|
||||
from ...utils.file import search_for, where_is
|
||||
from . import BaseDoc
|
||||
from ..menu import NumberOption, TextOption, EnumeratedListOption, \
|
||||
BooleanOption
|
||||
BooleanOption
|
||||
from ...constfunc import win
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
@@ -55,41 +56,41 @@ from ...constfunc import win
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
import logging
|
||||
log = logging.getLogger(".graphdoc")
|
||||
LOG = logging.getLogger(".graphdoc")
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Private Constants
|
||||
#
|
||||
#-------------------------------------------------------------------------------
|
||||
_FONTS = [ { 'name' : _("Default"), 'value' : "" },
|
||||
{ 'name' : _("PostScript / Helvetica"), 'value' : "Helvetica" },
|
||||
{ 'name' : _("TrueType / FreeSans"), 'value' : "FreeSans" } ]
|
||||
#-------------------------------------------------------------------------
|
||||
_FONTS = [{'name' : _("Default"), 'value' : ""},
|
||||
{'name' : _("PostScript / Helvetica"), 'value' : "Helvetica"},
|
||||
{'name' : _("TrueType / FreeSans"), 'value' : "FreeSans"}]
|
||||
|
||||
_RANKDIR = [ { 'name' : _("Vertical (↓)"), 'value' : "TB" },
|
||||
{ 'name' : _("Vertical (↑)"), 'value' : "BT" },
|
||||
{ 'name' : _("Horizontal (→)"), 'value' : "LR" },
|
||||
{ 'name' : _("Horizontal (←)"), 'value' : "RL" } ]
|
||||
_RANKDIR = [{'name' : _("Vertical (↓)"), 'value' : "TB"},
|
||||
{'name' : _("Vertical (↑)"), 'value' : "BT"},
|
||||
{'name' : _("Horizontal (→)"), 'value' : "LR"},
|
||||
{'name' : _("Horizontal (←)"), 'value' : "RL"}]
|
||||
|
||||
_PAGEDIR = [ { 'name' : _("Bottom, left"), 'value' :"BL" },
|
||||
{ 'name' : _("Bottom, right"), 'value' :"BR" },
|
||||
{ 'name' : _("Top, left"), 'value' :"TL" },
|
||||
{ 'name' : _("Top, Right"), 'value' :"TR" },
|
||||
{ 'name' : _("Right, bottom"), 'value' :"RB" },
|
||||
{ 'name' : _("Right, top"), 'value' :"RT" },
|
||||
{ 'name' : _("Left, bottom"), 'value' :"LB" },
|
||||
{ 'name' : _("Left, top"), 'value' :"LT" } ]
|
||||
_PAGEDIR = [{'name' : _("Bottom, left"), 'value' : "BL"},
|
||||
{'name' : _("Bottom, right"), 'value' : "BR"},
|
||||
{'name' : _("Top, left"), 'value' : "TL"},
|
||||
{'name' : _("Top, Right"), 'value' : "TR"},
|
||||
{'name' : _("Right, bottom"), 'value' : "RB"},
|
||||
{'name' : _("Right, top"), 'value' : "RT"},
|
||||
{'name' : _("Left, bottom"), 'value' : "LB"},
|
||||
{'name' : _("Left, top"), 'value' : "LT"}]
|
||||
|
||||
_RATIO = [ { 'name' : _("Compress to minimal size"), 'value': "compress" },
|
||||
{ 'name' : _("Fill the given area"), 'value': "fill" },
|
||||
{ 'name' : _("Expand uniformly"), 'value': "expand" } ]
|
||||
_RATIO = [{'name' : _("Compress to minimal size"), 'value': "compress"},
|
||||
{'name' : _("Fill the given area"), 'value': "fill"},
|
||||
{'name' : _("Expand uniformly"), 'value': "expand"}]
|
||||
|
||||
_NOTELOC = [ { 'name' : _("Top"), 'value' : "t" },
|
||||
{ 'name' : _("Bottom"), 'value' : "b" }]
|
||||
_NOTELOC = [{'name' : _("Top"), 'value' : "t"},
|
||||
{'name' : _("Bottom"), 'value' : "b"}]
|
||||
|
||||
_SPLINE = [ { 'name' : _("Straight"), 'value' : "false" },
|
||||
{ 'name' : _("Curved"), 'value' : "true", },
|
||||
{ 'name' : _("Orthogonal"), 'value' : 'ortho'} ]
|
||||
_SPLINE = [{'name' : _("Straight"), 'value' : "false"},
|
||||
{'name' : _("Curved"), 'value' : "true", },
|
||||
{'name' : _("Orthogonal"), 'value' : 'ortho'}]
|
||||
|
||||
if win():
|
||||
_DOT_FOUND = search_for("dot.exe")
|
||||
@@ -102,26 +103,23 @@ if win():
|
||||
_GS_CMD = ""
|
||||
else:
|
||||
_DOT_FOUND = search_for("dot")
|
||||
_GS_CMD = where_is("gs")
|
||||
|
||||
if search_for("gs") == 1:
|
||||
_GS_CMD = "gs"
|
||||
else:
|
||||
_GS_CMD = ""
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
#------------------------------------------------------------------------------
|
||||
#
|
||||
# GVOptions
|
||||
#
|
||||
#-------------------------------------------------------------------------------
|
||||
#------------------------------------------------------------------------------
|
||||
class GVOptions:
|
||||
"""
|
||||
Defines all of the controls necessary
|
||||
to configure the graph reports.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.h_pages = None
|
||||
self.v_pages = None
|
||||
self.page_dir = None
|
||||
self.h_pages = None
|
||||
self.v_pages = None
|
||||
self.page_dir = None
|
||||
self.dpi = None
|
||||
|
||||
def add_menu_options(self, menu):
|
||||
@@ -139,9 +137,9 @@ class GVOptions:
|
||||
for item in _FONTS:
|
||||
font_family.add_item(item["value"], item["name"])
|
||||
font_family.set_help(_("Choose the font family. If international "
|
||||
"characters don't show, use FreeSans font. "
|
||||
"FreeSans is available from: "
|
||||
"http://www.nongnu.org/freefont/"))
|
||||
"characters don't show, use FreeSans font. "
|
||||
"FreeSans is available from: "
|
||||
"http://www.nongnu.org/freefont/"))
|
||||
menu.add_option(category, "font_family", font_family)
|
||||
|
||||
font_size = NumberOption(_("Font size"), 14, 8, 128)
|
||||
@@ -188,9 +186,9 @@ class GVOptions:
|
||||
# the page direction option only makes sense when the
|
||||
# number of horizontal and/or vertical pages is > 1,
|
||||
# so we need to remember these 3 controls for later
|
||||
self.h_pages = h_pages
|
||||
self.v_pages = v_pages
|
||||
self.page_dir = page_dir
|
||||
self.h_pages = h_pages
|
||||
self.v_pages = v_pages
|
||||
self.page_dir = page_dir
|
||||
|
||||
# the page direction option only makes sense when the
|
||||
# number of horizontal and/or vertical pages is > 1
|
||||
@@ -204,7 +202,8 @@ class GVOptions:
|
||||
aspect_ratio = EnumeratedListOption(_("Aspect ratio"), "fill")
|
||||
for item in _RATIO:
|
||||
aspect_ratio.add_item(item["value"], item["name"])
|
||||
help_text = _('Affects node spacing and scaling of the graph.\n'
|
||||
help_text = _(
|
||||
'Affects node spacing and scaling of the graph.\n'
|
||||
'If the graph is smaller than the print area:\n'
|
||||
' Compress will not change the node spacing. \n'
|
||||
' Fill will increase the node spacing to fit the print area in '
|
||||
@@ -221,34 +220,34 @@ class GVOptions:
|
||||
menu.add_option(category, "ratio", aspect_ratio)
|
||||
|
||||
dpi = NumberOption(_("DPI"), 75, 20, 1200)
|
||||
dpi.set_help(_( "Dots per inch. When creating images such as "
|
||||
".gif or .png files for the web, try numbers "
|
||||
"such as 100 or 300 DPI. PostScript and PDF files "
|
||||
"always use 72 DPI."))
|
||||
dpi.set_help(_("Dots per inch. When creating images such as "
|
||||
".gif or .png files for the web, try numbers "
|
||||
"such as 100 or 300 DPI. PostScript and PDF files "
|
||||
"always use 72 DPI."))
|
||||
menu.add_option(category, "dpi", dpi)
|
||||
self.dpi = dpi
|
||||
|
||||
nodesep = NumberOption(_("Node spacing"), 0.20, 0.01, 5.00, 0.01)
|
||||
nodesep.set_help(_( "The minimum amount of free space, in inches, "
|
||||
"between individual nodes. For vertical graphs, "
|
||||
"this corresponds to spacing between columns. "
|
||||
"For horizontal graphs, this corresponds to "
|
||||
"spacing between rows."))
|
||||
nodesep.set_help(_("The minimum amount of free space, in inches, "
|
||||
"between individual nodes. For vertical graphs, "
|
||||
"this corresponds to spacing between columns. "
|
||||
"For horizontal graphs, this corresponds to "
|
||||
"spacing between rows."))
|
||||
menu.add_option(category, "nodesep", nodesep)
|
||||
|
||||
ranksep = NumberOption(_("Rank spacing"), 0.20, 0.01, 5.00, 0.01)
|
||||
ranksep.set_help(_( "The minimum amount of free space, in inches, "
|
||||
"between ranks. For vertical graphs, this "
|
||||
"corresponds to spacing between rows. For "
|
||||
"horizontal graphs, this corresponds to spacing "
|
||||
"between columns."))
|
||||
ranksep.set_help(_("The minimum amount of free space, in inches, "
|
||||
"between ranks. For vertical graphs, this "
|
||||
"corresponds to spacing between rows. For "
|
||||
"horizontal graphs, this corresponds to spacing "
|
||||
"between columns."))
|
||||
menu.add_option(category, "ranksep", ranksep)
|
||||
|
||||
use_subgraphs = BooleanOption(_('Use subgraphs'), True)
|
||||
use_subgraphs.set_help(_("Subgraphs can help Graphviz position "
|
||||
"spouses together, but with non-trivial "
|
||||
"graphs will result in longer lines and "
|
||||
"larger graphs."))
|
||||
"spouses together, but with non-trivial "
|
||||
"graphs will result in longer lines and "
|
||||
"larger graphs."))
|
||||
menu.add_option(category, "usesubgraphs", use_subgraphs)
|
||||
|
||||
################################
|
||||
@@ -256,15 +255,15 @@ class GVOptions:
|
||||
################################
|
||||
|
||||
note = TextOption(_("Note to add to the graph"),
|
||||
[""] )
|
||||
[""])
|
||||
note.set_help(_("This text will be added to the graph."))
|
||||
menu.add_option(category, "note", note)
|
||||
|
||||
noteloc = EnumeratedListOption(_("Note location"), 't')
|
||||
for i in range( 0, len(_NOTELOC) ):
|
||||
for i in range(0, len(_NOTELOC)):
|
||||
noteloc.add_item(_NOTELOC[i]["value"], _NOTELOC[i]["name"])
|
||||
noteloc.set_help(_("Whether note will appear on top "
|
||||
"or bottom of the page."))
|
||||
"or bottom of the page."))
|
||||
menu.add_option(category, "noteloc", noteloc)
|
||||
|
||||
notesize = NumberOption(_("Note size"), 32, 8, 128)
|
||||
@@ -278,17 +277,17 @@ class GVOptions:
|
||||
pages are set to "1", then the page_dir control needs to
|
||||
be unavailable
|
||||
"""
|
||||
if self.v_pages.get_value() > 1 or \
|
||||
self.h_pages.get_value() > 1:
|
||||
if self.v_pages.get_value() > 1 or self.h_pages.get_value() > 1:
|
||||
self.page_dir.set_available(True)
|
||||
else:
|
||||
self.page_dir.set_available(False)
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
#
|
||||
# GVDoc
|
||||
#
|
||||
#-------------------------------------------------------------------------------
|
||||
#------------------------------------------------------------------------------
|
||||
class GVDoc(metaclass=ABCMeta):
|
||||
"""
|
||||
Abstract Interface for Graphviz document generators. Output formats
|
||||
@@ -374,11 +373,12 @@ class GVDoc(metaclass=ABCMeta):
|
||||
:return: nothing
|
||||
"""
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
#
|
||||
# GVDocBase
|
||||
#
|
||||
#-------------------------------------------------------------------------------
|
||||
#------------------------------------------------------------------------------
|
||||
class GVDocBase(BaseDoc, GVDoc):
|
||||
"""
|
||||
Base document generator for all Graphviz document generators. Classes that
|
||||
@@ -388,40 +388,39 @@ class GVDocBase(BaseDoc, GVDoc):
|
||||
def __init__(self, options, paper_style, uistate=None):
|
||||
BaseDoc.__init__(self, None, paper_style, uistate=uistate)
|
||||
|
||||
self._filename = None
|
||||
self._filename = None
|
||||
self._dot = BytesIO()
|
||||
self._paper = paper_style
|
||||
self._paper = paper_style
|
||||
|
||||
get_option_by_name = options.menu.get_option_by_name
|
||||
get_value = lambda name: get_option_by_name(name).get_value()
|
||||
get_option = options.menu.get_option_by_name
|
||||
|
||||
self.dpi = get_value('dpi')
|
||||
self.fontfamily = get_value('font_family')
|
||||
self.fontsize = get_value('font_size')
|
||||
self.hpages = get_value('h_pages')
|
||||
self.nodesep = get_value('nodesep')
|
||||
self.noteloc = get_value('noteloc')
|
||||
self.notesize = get_value('notesize')
|
||||
self.note = get_value('note')
|
||||
self.pagedir = get_value('page_dir')
|
||||
self.rankdir = get_value('rank_dir')
|
||||
self.ranksep = get_value('ranksep')
|
||||
self.ratio = get_value('ratio')
|
||||
self.vpages = get_value('v_pages')
|
||||
self.usesubgraphs = get_value('usesubgraphs')
|
||||
self.spline = get_value('spline')
|
||||
self.dpi = get_option('dpi').get_value()
|
||||
self.fontfamily = get_option('font_family').get_value()
|
||||
self.fontsize = get_option('font_size').get_value()
|
||||
self.hpages = get_option('h_pages').get_value()
|
||||
self.nodesep = get_option('nodesep').get_value()
|
||||
self.noteloc = get_option('noteloc').get_value()
|
||||
self.notesize = get_option('notesize').get_value()
|
||||
self.note = get_option('note').get_value()
|
||||
self.pagedir = get_option('page_dir').get_value()
|
||||
self.rankdir = get_option('rank_dir').get_value()
|
||||
self.ranksep = get_option('ranksep').get_value()
|
||||
self.ratio = get_option('ratio').get_value()
|
||||
self.vpages = get_option('v_pages').get_value()
|
||||
self.usesubgraphs = get_option('usesubgraphs').get_value()
|
||||
self.spline = get_option('spline').get_value()
|
||||
|
||||
paper_size = paper_style.get_size()
|
||||
paper_size = paper_style.get_size()
|
||||
|
||||
# Subtract 0.01" from the drawing area to make some room between
|
||||
# this area and the margin in order to compensate for different
|
||||
# rounding errors internally in dot
|
||||
sizew = ( paper_size.get_width() -
|
||||
self._paper.get_left_margin() -
|
||||
self._paper.get_right_margin() ) / 2.54 - 0.01
|
||||
sizeh = ( paper_size.get_height() -
|
||||
self._paper.get_top_margin() -
|
||||
self._paper.get_bottom_margin() ) / 2.54 - 0.01
|
||||
sizew = (paper_size.get_width() -
|
||||
self._paper.get_left_margin() -
|
||||
self._paper.get_right_margin()) / 2.54 - 0.01
|
||||
sizeh = (paper_size.get_height() -
|
||||
self._paper.get_top_margin() -
|
||||
self._paper.get_bottom_margin()) / 2.54 - 0.01
|
||||
|
||||
pheight = paper_size.get_height_inches()
|
||||
pwidth = paper_size.get_width_inches()
|
||||
@@ -438,33 +437,33 @@ class GVDocBase(BaseDoc, GVDoc):
|
||||
' bgcolor=white;\n'
|
||||
' center="true"; \n'
|
||||
' charset="utf8";\n'
|
||||
' concentrate="false";\n' +
|
||||
' dpi="%d";\n' % self.dpi +
|
||||
' graph [fontsize=%d];\n' % self.fontsize +
|
||||
' margin="%3.2f,%3.2f"; \n' % (xmargin, ymargin) +
|
||||
' mclimit="99";\n' +
|
||||
' nodesep="%.2f";\n' % self.nodesep +
|
||||
' concentrate="false";\n' +
|
||||
' dpi="%d";\n' % self.dpi +
|
||||
' graph [fontsize=%d];\n' % self.fontsize +
|
||||
' margin="%3.2f,%3.2f"; \n' % (xmargin, ymargin) +
|
||||
' mclimit="99";\n' +
|
||||
' nodesep="%.2f";\n' % self.nodesep +
|
||||
' outputorder="edgesfirst";\n' +
|
||||
('#' if self.hpages == self.vpages == 1 else '') +
|
||||
# comment out "page=" if the graph is on 1 page (bug #2121)
|
||||
' page="%3.2f,%3.2f";\n' % (pwidth, pheight) +
|
||||
' pagedir="%s";\n' % self.pagedir +
|
||||
' rankdir="%s";\n' % self.rankdir +
|
||||
' ranksep="%.2f";\n' % self.ranksep +
|
||||
' ratio="%s";\n' % self.ratio +
|
||||
' searchsize="100";\n' +
|
||||
' size="%3.2f,%3.2f"; \n' % (sizew, sizeh) +
|
||||
' splines="%s";\n' % self.spline +
|
||||
'\n' +
|
||||
' edge [len=0.5 style=solid fontsize=%d];\n' % self.fontsize
|
||||
)
|
||||
# comment out "page=" if the graph is on 1 page (bug #2121)
|
||||
' page="%3.2f,%3.2f";\n' % (pwidth, pheight) +
|
||||
' pagedir="%s";\n' % self.pagedir +
|
||||
' rankdir="%s";\n' % self.rankdir +
|
||||
' ranksep="%.2f";\n' % self.ranksep +
|
||||
' ratio="%s";\n' % self.ratio +
|
||||
' searchsize="100";\n' +
|
||||
' size="%3.2f,%3.2f"; \n' % (sizew, sizeh) +
|
||||
' splines="%s";\n' % self.spline +
|
||||
'\n' +
|
||||
' edge [len=0.5 style=solid fontsize=%d];\n' % self.fontsize)
|
||||
|
||||
if self.fontfamily:
|
||||
self.write( ' node [style=filled fontname="%s" fontsize=%d];\n'
|
||||
% ( self.fontfamily, self.fontsize ) )
|
||||
self.write(' node [style=filled fontname="%s" fontsize=%d];\n'
|
||||
% (self.fontfamily, self.fontsize))
|
||||
else:
|
||||
self.write( ' node [style=filled fontsize=%d];\n'
|
||||
% self.fontsize )
|
||||
self.write( '\n' )
|
||||
self.write(' node [style=filled fontsize=%d];\n'
|
||||
% self.fontsize)
|
||||
self.write('\n')
|
||||
|
||||
def write(self, text):
|
||||
""" Write text to the dot file """
|
||||
@@ -482,10 +481,10 @@ class GVDocBase(BaseDoc, GVDoc):
|
||||
if self.note:
|
||||
# build up the label
|
||||
label = ''
|
||||
for line in self.note: # for every line in the note...
|
||||
line = line.strip() # ...strip whitespace from this line...
|
||||
if line != '': # ...and if we still have a line...
|
||||
if label != '': # ...see if we need to insert a newline...
|
||||
for line in self.note: # for every line in the note...
|
||||
line = line.strip() # ...strip whitespace from this line...
|
||||
if line != '': # ...and if we still have a line...
|
||||
if label != '': # ...see if we need to insert a newline...
|
||||
label += '\\n'
|
||||
label += line.replace('"', '\\\"')
|
||||
|
||||
@@ -493,12 +492,11 @@ class GVDocBase(BaseDoc, GVDoc):
|
||||
if label != '':
|
||||
self.write(
|
||||
'\n' +
|
||||
' label="%s";\n' % label +
|
||||
' labelloc="%s";\n' % self.noteloc +
|
||||
' fontsize="%d";\n' % self.notesize
|
||||
)
|
||||
' label="%s";\n' % label +
|
||||
' labelloc="%s";\n' % self.noteloc +
|
||||
' fontsize="%d";\n' % self.notesize)
|
||||
|
||||
self.write( '}\n\n' )
|
||||
self.write('}\n\n')
|
||||
|
||||
def add_node(self, node_id, label, shape="", color="",
|
||||
style="", fillcolor="", url="", htmloutput=False):
|
||||
@@ -511,27 +509,27 @@ class GVDocBase(BaseDoc, GVDoc):
|
||||
text = '['
|
||||
|
||||
if shape:
|
||||
text += ' shape="%s"' % shape
|
||||
text += ' shape="%s"' % shape
|
||||
|
||||
if color:
|
||||
text += ' color="%s"' % color
|
||||
text += ' color="%s"' % color
|
||||
|
||||
if fillcolor:
|
||||
text += ' fillcolor="%s"' % fillcolor
|
||||
text += ' fillcolor="%s"' % fillcolor
|
||||
|
||||
if style:
|
||||
text += ' style="%s"' % style
|
||||
text += ' style="%s"' % style
|
||||
|
||||
# note that we always output a label -- even if an empty string --
|
||||
# otherwise Graphviz uses the node ID as the label which is unlikely
|
||||
# to be what the user wants to see in the graph
|
||||
if label.startswith("<") or htmloutput:
|
||||
text += ' label=<%s>' % label
|
||||
text += ' label=<%s>' % label
|
||||
else:
|
||||
text += ' label="%s"' % label
|
||||
text += ' label="%s"' % label
|
||||
|
||||
if url:
|
||||
text += ' URL="%s"' % url
|
||||
text += ' URL="%s"' % url
|
||||
|
||||
text += " ]"
|
||||
self.write(' "%s" %s;\n' % (node_id, text))
|
||||
@@ -590,22 +588,22 @@ class GVDocBase(BaseDoc, GVDoc):
|
||||
|
||||
def start_subgraph(self, graph_id):
|
||||
""" Implement GVDocBase.start_subgraph() """
|
||||
graph_id = graph_id.replace(' ', '_') # for user-defined ID with space
|
||||
graph_id = graph_id.replace(' ', '_') # for user-defined ID with space
|
||||
self.write(
|
||||
' subgraph cluster_%s\n' % graph_id +
|
||||
' {\n' +
|
||||
' style="invis";\n' # no border around subgraph (#0002176)
|
||||
)
|
||||
' style="invis";\n') # no border around subgraph (#0002176)
|
||||
|
||||
def end_subgraph(self):
|
||||
""" Implement GVDocBase.end_subgraph() """
|
||||
self.write(' }\n')
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
#
|
||||
# GVDotDoc
|
||||
#
|
||||
#-------------------------------------------------------------------------------
|
||||
#------------------------------------------------------------------------------
|
||||
class GVDotDoc(GVDocBase):
|
||||
""" GVDoc implementation that generates a .gv text file. """
|
||||
|
||||
@@ -620,11 +618,12 @@ class GVDotDoc(GVDocBase):
|
||||
with open(self._filename, "wb") as dotfile:
|
||||
dotfile.write(self._dot.getvalue())
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
#
|
||||
# GVPsDoc
|
||||
#
|
||||
#-------------------------------------------------------------------------------
|
||||
#------------------------------------------------------------------------------
|
||||
class GVPsDoc(GVDocBase):
|
||||
""" GVDoc implementation that generates a .ps file using Graphviz. """
|
||||
|
||||
@@ -650,7 +649,7 @@ class GVPsDoc(GVDocBase):
|
||||
self._filename += ".ps"
|
||||
|
||||
# Create a temporary dot file
|
||||
(handle, tmp_dot) = tempfile.mkstemp(".gv" )
|
||||
(handle, tmp_dot) = tempfile.mkstemp(".gv")
|
||||
dotfile = os.fdopen(handle, "wb")
|
||||
dotfile.write(self._dot.getvalue())
|
||||
dotfile.close()
|
||||
@@ -667,23 +666,28 @@ class GVPsDoc(GVDocBase):
|
||||
# disappeared. I used 1 inch margins always.
|
||||
# See bug tracker issue 2815
|
||||
# :cairo does not work with Graphviz 2.26.3 and later See issue 4164
|
||||
# recent versions of Graphviz doesn't even try, just puts out a single
|
||||
# large page.
|
||||
|
||||
command = 'dot -Tps:cairo -o"%s" "%s"' % (self._filename, tmp_dot)
|
||||
dotversion = str(Popen(['dot', '-V'], stderr=PIPE).communicate(input=None)[1])
|
||||
# Problem with dot 2.26.3 and later and multiple pages, which gives "cairo: out of
|
||||
# memory" If the :cairo is skipped for these cases it gives acceptable
|
||||
# result.
|
||||
if (dotversion.find('2.26.3') or dotversion.find('2.28.0') != -1) and (self.vpages * self.hpages) > 1:
|
||||
command = command.replace(':cairo','')
|
||||
dotversion = str(Popen(['dot', '-V'],
|
||||
stderr=PIPE).communicate(input=None)[1])
|
||||
# Problem with dot 2.26.3 and later and multiple pages, which gives
|
||||
# "cairo: out of memory" If the :cairo is skipped for these cases it
|
||||
# gives bad result for non-Latin-1 characters (utf-8).
|
||||
if (dotversion.find('2.26.3') or dotversion.find('2.28.0') != -1) and \
|
||||
(self.vpages * self.hpages) > 1:
|
||||
command = command.replace(':cairo', '')
|
||||
os.system(command)
|
||||
# Delete the temporary dot file
|
||||
os.remove(tmp_dot)
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
#
|
||||
# GVSvgDoc
|
||||
#
|
||||
#-------------------------------------------------------------------------------
|
||||
#------------------------------------------------------------------------------
|
||||
class GVSvgDoc(GVDocBase):
|
||||
""" GVDoc implementation that generates a .svg file using Graphviz. """
|
||||
|
||||
@@ -703,21 +707,22 @@ class GVSvgDoc(GVDocBase):
|
||||
self._filename += ".svg"
|
||||
|
||||
# Create a temporary dot file
|
||||
(handle, tmp_dot) = tempfile.mkstemp(".gv" )
|
||||
(handle, tmp_dot) = tempfile.mkstemp(".gv")
|
||||
dotfile = os.fdopen(handle, "wb")
|
||||
dotfile.write(self._dot.getvalue())
|
||||
dotfile.close()
|
||||
# Generate the SVG file.
|
||||
os.system( 'dot -Tsvg:cairo -o"%s" "%s"' % (self._filename, tmp_dot) )
|
||||
os.system('dot -Tsvg:cairo -o"%s" "%s"' % (self._filename, tmp_dot))
|
||||
|
||||
# Delete the temporary dot file
|
||||
os.remove(tmp_dot)
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
#
|
||||
# GVSvgzDoc
|
||||
#
|
||||
#-------------------------------------------------------------------------------
|
||||
#------------------------------------------------------------------------------
|
||||
class GVSvgzDoc(GVDocBase):
|
||||
""" GVDoc implementation that generates a .svg file using Graphviz. """
|
||||
|
||||
@@ -737,21 +742,22 @@ class GVSvgzDoc(GVDocBase):
|
||||
self._filename += ".svgz"
|
||||
|
||||
# Create a temporary dot file
|
||||
(handle, tmp_dot) = tempfile.mkstemp(".gv" )
|
||||
(handle, tmp_dot) = tempfile.mkstemp(".gv")
|
||||
dotfile = os.fdopen(handle, "wb")
|
||||
dotfile.write(self._dot.getvalue())
|
||||
dotfile.close()
|
||||
# Generate the SVGZ file.
|
||||
os.system( 'dot -Tsvgz -o"%s" "%s"' % (self._filename, tmp_dot) )
|
||||
os.system('dot -Tsvgz -o"%s" "%s"' % (self._filename, tmp_dot))
|
||||
|
||||
# Delete the temporary dot file
|
||||
os.remove(tmp_dot)
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
#
|
||||
# GVPngDoc
|
||||
#
|
||||
#-------------------------------------------------------------------------------
|
||||
#------------------------------------------------------------------------------
|
||||
class GVPngDoc(GVDocBase):
|
||||
""" GVDoc implementation that generates a .png file using Graphviz. """
|
||||
|
||||
@@ -771,21 +777,22 @@ class GVPngDoc(GVDocBase):
|
||||
self._filename += ".png"
|
||||
|
||||
# Create a temporary dot file
|
||||
(handle, tmp_dot) = tempfile.mkstemp(".gv" )
|
||||
(handle, tmp_dot) = tempfile.mkstemp(".gv")
|
||||
dotfile = os.fdopen(handle, "wb")
|
||||
dotfile.write(self._dot.getvalue())
|
||||
dotfile.close()
|
||||
# Generate the PNG file.
|
||||
os.system( 'dot -Tpng -o"%s" "%s"' % (self._filename, tmp_dot) )
|
||||
os.system('dot -Tpng -o"%s" "%s"' % (self._filename, tmp_dot))
|
||||
|
||||
# Delete the temporary dot file
|
||||
os.remove(tmp_dot)
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
#
|
||||
# GVJpegDoc
|
||||
#
|
||||
#-------------------------------------------------------------------------------
|
||||
#------------------------------------------------------------------------------
|
||||
class GVJpegDoc(GVDocBase):
|
||||
""" GVDoc implementation that generates a .jpg file using Graphviz. """
|
||||
|
||||
@@ -805,21 +812,22 @@ class GVJpegDoc(GVDocBase):
|
||||
self._filename += ".jpg"
|
||||
|
||||
# Create a temporary dot file
|
||||
(handle, tmp_dot) = tempfile.mkstemp(".gv" )
|
||||
(handle, tmp_dot) = tempfile.mkstemp(".gv")
|
||||
dotfile = os.fdopen(handle, "wb")
|
||||
dotfile.write(self._dot.getvalue())
|
||||
dotfile.close()
|
||||
# Generate the JPEG file.
|
||||
os.system( 'dot -Tjpg -o"%s" "%s"' % (self._filename, tmp_dot) )
|
||||
os.system('dot -Tjpg -o"%s" "%s"' % (self._filename, tmp_dot))
|
||||
|
||||
# Delete the temporary dot file
|
||||
os.remove(tmp_dot)
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
#
|
||||
# GVGifDoc
|
||||
#
|
||||
#-------------------------------------------------------------------------------
|
||||
#------------------------------------------------------------------------------
|
||||
class GVGifDoc(GVDocBase):
|
||||
""" GVDoc implementation that generates a .gif file using Graphviz. """
|
||||
|
||||
@@ -839,21 +847,22 @@ class GVGifDoc(GVDocBase):
|
||||
self._filename += ".gif"
|
||||
|
||||
# Create a temporary dot file
|
||||
(handle, tmp_dot) = tempfile.mkstemp(".gv" )
|
||||
(handle, tmp_dot) = tempfile.mkstemp(".gv")
|
||||
dotfile = os.fdopen(handle, "wb")
|
||||
dotfile.write(self._dot.getvalue())
|
||||
dotfile.close()
|
||||
# Generate the GIF file.
|
||||
os.system( 'dot -Tgif -o"%s" "%s"' % (self._filename, tmp_dot) )
|
||||
os.system('dot -Tgif -o"%s" "%s"' % (self._filename, tmp_dot))
|
||||
|
||||
# Delete the temporary dot file
|
||||
os.remove(tmp_dot)
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
#
|
||||
# GVPdfGvDoc
|
||||
#
|
||||
#-------------------------------------------------------------------------------
|
||||
#------------------------------------------------------------------------------
|
||||
class GVPdfGvDoc(GVDocBase):
|
||||
""" GVDoc implementation that generates a .pdf file using Graphviz. """
|
||||
|
||||
@@ -876,23 +885,24 @@ class GVPdfGvDoc(GVDocBase):
|
||||
self._filename += ".pdf"
|
||||
|
||||
# Create a temporary dot file
|
||||
(handle, tmp_dot) = tempfile.mkstemp(".gv" )
|
||||
(handle, tmp_dot) = tempfile.mkstemp(".gv")
|
||||
dotfile = os.fdopen(handle, "wb")
|
||||
dotfile.write(self._dot.getvalue())
|
||||
dotfile.close()
|
||||
fname = self._filename
|
||||
|
||||
# Generate the PDF file.
|
||||
os.system( 'dot -Tpdf -o"%s" "%s"' % (fname, tmp_dot) )
|
||||
os.system('dot -Tpdf -o"%s" "%s"' % (fname, tmp_dot))
|
||||
|
||||
# Delete the temporary dot file
|
||||
os.remove(tmp_dot)
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
#
|
||||
# GVPdfGsDoc
|
||||
#
|
||||
#-------------------------------------------------------------------------------
|
||||
#------------------------------------------------------------------------------
|
||||
class GVPdfGsDoc(GVDocBase):
|
||||
""" GVDoc implementation that generates a .pdf file using Ghostscript. """
|
||||
def __init__(self, options, paper_style):
|
||||
@@ -910,103 +920,144 @@ class GVPdfGsDoc(GVDocBase):
|
||||
self._filename += ".pdf"
|
||||
|
||||
# Create a temporary dot file
|
||||
(handle, tmp_dot) = tempfile.mkstemp(".gv" )
|
||||
(handle, tmp_dot) = tempfile.mkstemp(".gv")
|
||||
dotfile = os.fdopen(handle, "wb")
|
||||
dotfile.write(self._dot.getvalue())
|
||||
dotfile.close()
|
||||
|
||||
# Create a temporary PostScript file
|
||||
(handle, tmp_ps) = tempfile.mkstemp(".ps" )
|
||||
os.close( handle )
|
||||
(handle, tmp_ps) = tempfile.mkstemp(".ps")
|
||||
os.close(handle)
|
||||
|
||||
# Generate PostScript using dot
|
||||
# Reason for using -Tps:cairo. Needed for Non Latin-1 letters
|
||||
# See bug tracker issue 2815
|
||||
# :cairo does not work with Graphviz 2.26.3 and later See issue 4164
|
||||
# :cairo does not work with with multi-page See issue 4164
|
||||
# recent versions of Graphviz doesn't even try, just puts out a single
|
||||
# large page, so we use Ghostscript to split it up.
|
||||
|
||||
command = 'dot -Tps:cairo -o"%s" "%s"' % ( tmp_ps, tmp_dot )
|
||||
dotversion = str(Popen(['dot', '-V'], stderr=PIPE).communicate(input=None)[1])
|
||||
# Problem with dot 2.26.3 and later and multiple pages, which gives "cairo: out
|
||||
# of memory". If the :cairo is skipped for these cases it gives
|
||||
# acceptable result.
|
||||
if (dotversion.find('2.26.3') or dotversion.find('2.28.0') != -1) and (self.vpages * self.hpages) > 1:
|
||||
command = command.replace(':cairo','')
|
||||
command = 'dot -Tps:cairo -o"%s" "%s"' % (tmp_ps, tmp_dot)
|
||||
os.system(command)
|
||||
|
||||
# Add .5 to remove rounding errors.
|
||||
paper_size = self._paper.get_size()
|
||||
width_pt = int( (paper_size.get_width_inches() * 72) + 0.5 )
|
||||
height_pt = int( (paper_size.get_height_inches() * 72) + 0.5 )
|
||||
|
||||
width_pt = int((paper_size.get_width_inches() * 72) + .5)
|
||||
height_pt = int((paper_size.get_height_inches() * 72) + .5)
|
||||
if (self.vpages * self.hpages) == 1:
|
||||
# -dDEVICEWIDTHPOINTS=%d' -dDEVICEHEIGHTPOINTS=%d
|
||||
command = '%s -q -sDEVICE=pdfwrite -dNOPAUSE '\
|
||||
'-dDEVICEWIDTHPOINTS=%d -dDEVICEHEIGHTPOINTS=%d '\
|
||||
'-sOutputFile="%s" "%s" -c quit' % (
|
||||
_GS_CMD, width_pt, height_pt, self._filename, tmp_ps)
|
||||
os.system(command)
|
||||
os.remove(tmp_ps)
|
||||
return
|
||||
# Margins (in centimeters) to pixels 72/2.54=28.345
|
||||
margin_t = int(28.345 * self._paper.get_top_margin())
|
||||
margin_b = int(28.345 * self._paper.get_bottom_margin())
|
||||
margin_r = int(28.345 * self._paper.get_right_margin())
|
||||
margin_l = int(28.345 * self._paper.get_left_margin())
|
||||
margin_x = margin_l + margin_r
|
||||
margin_y = margin_t + margin_b
|
||||
# Convert to PDF using ghostscript
|
||||
command = '%s -q -sDEVICE=pdfwrite -dNOPAUSE -dDEVICEWIDTHPOINTS=%d' \
|
||||
' -dDEVICEHEIGHTPOINTS=%d -sOutputFile="%s" "%s" -c quit' \
|
||||
% ( _GS_CMD, width_pt, height_pt, self._filename, tmp_ps )
|
||||
list_of_pieces = []
|
||||
|
||||
x_rng = range(1, self.hpages + 1) if 'L' in self.pagedir \
|
||||
else range(self.hpages, 0, -1)
|
||||
y_rng = range(1, self.vpages + 1) if 'B' in self.pagedir \
|
||||
else range(self.vpages, 0, -1)
|
||||
if self.pagedir[0] in 'TB':
|
||||
the_list = ((__x, __y) for __y in y_rng for __x in x_rng)
|
||||
else:
|
||||
the_list = ((__x, __y) for __x in x_rng for __y in y_rng)
|
||||
for __x, __y in the_list:
|
||||
# Slit PS file to pieces of PDF
|
||||
page_offset_x = (__x - 1) * (margin_x - width_pt)
|
||||
page_offset_y = (__y - 1) * (margin_y - height_pt)
|
||||
tmp_pdf_piece = "%s_%d_%d.pdf" % (tmp_ps, __x, __y)
|
||||
list_of_pieces.append(tmp_pdf_piece)
|
||||
# Generate Ghostscript code
|
||||
command = '%s -q -dBATCH -dNOPAUSE -dSAFER -g%dx%d '\
|
||||
'-sOutputFile="%s" -r72 -sDEVICE=pdfwrite '\
|
||||
'-c "<</.HWMargins [%d %d %d %d] /PageOffset [%d %d]>> '\
|
||||
'setpagedevice" -f "%s"' % (
|
||||
_GS_CMD, width_pt + 10, height_pt + 10, tmp_pdf_piece,
|
||||
margin_l, margin_b, margin_r, margin_t,
|
||||
page_offset_x + 5, page_offset_y + 5, tmp_ps)
|
||||
# Execute Ghostscript
|
||||
os.system(command)
|
||||
# Merge pieces to single multipage PDF ;
|
||||
command = '%s -q -dBATCH -dNOPAUSE '\
|
||||
'-sOUTPUTFILE=%s -r72 -sDEVICE=pdfwrite %s '\
|
||||
% (_GS_CMD, self._filename, ' '.join(list_of_pieces))
|
||||
os.system(command)
|
||||
|
||||
# Clean temporary files
|
||||
os.remove(tmp_ps)
|
||||
for tmp_pdf_piece in list_of_pieces:
|
||||
os.remove(tmp_pdf_piece)
|
||||
os.remove(tmp_dot)
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
#------------------------------------------------------------------------------
|
||||
#
|
||||
# Various Graphviz formats.
|
||||
#
|
||||
#-------------------------------------------------------------------------------
|
||||
#------------------------------------------------------------------------------
|
||||
FORMATS = []
|
||||
|
||||
if _DOT_FOUND:
|
||||
|
||||
if _GS_CMD != "":
|
||||
FORMATS += [{ 'type' : "gspdf",
|
||||
'ext' : "pdf",
|
||||
'descr': _("PDF (Ghostscript)"),
|
||||
'mime' : "application/pdf",
|
||||
'class': GVPdfGsDoc }]
|
||||
FORMATS += [{'type' : "gspdf",
|
||||
'ext' : "pdf",
|
||||
'descr': _("PDF (Ghostscript)"),
|
||||
'mime' : "application/pdf",
|
||||
'class': GVPdfGsDoc}]
|
||||
|
||||
FORMATS += [{ 'type' : "gvpdf",
|
||||
'ext' : "pdf",
|
||||
'descr': _("PDF (Graphviz)"),
|
||||
'mime' : "application/pdf",
|
||||
'class': GVPdfGvDoc }]
|
||||
FORMATS += [{'type' : "gvpdf",
|
||||
'ext' : "pdf",
|
||||
'descr': _("PDF (Graphviz)"),
|
||||
'mime' : "application/pdf",
|
||||
'class': GVPdfGvDoc}]
|
||||
|
||||
FORMATS += [{ 'type' : "ps",
|
||||
'ext' : "ps",
|
||||
'descr': _("PostScript"),
|
||||
'mime' : "application/postscript",
|
||||
'class': GVPsDoc }]
|
||||
FORMATS += [{'type' : "ps",
|
||||
'ext' : "ps",
|
||||
'descr': _("PostScript"),
|
||||
'mime' : "application/postscript",
|
||||
'class': GVPsDoc}]
|
||||
|
||||
FORMATS += [{ 'type' : "svg",
|
||||
'ext' : "svg",
|
||||
'descr': _("Structured Vector Graphics (SVG)"),
|
||||
'mime' : "image/svg",
|
||||
'class': GVSvgDoc }]
|
||||
FORMATS += [{'type' : "svg",
|
||||
'ext' : "svg",
|
||||
'descr': _("Structured Vector Graphics (SVG)"),
|
||||
'mime' : "image/svg",
|
||||
'class': GVSvgDoc}]
|
||||
|
||||
FORMATS += [{ 'type' : "svgz",
|
||||
'ext' : "svgz",
|
||||
'descr': _("Compressed Structured Vector Graphs (SVGZ)"),
|
||||
'mime' : "image/svgz",
|
||||
'class': GVSvgzDoc }]
|
||||
FORMATS += [{'type' : "svgz",
|
||||
'ext' : "svgz",
|
||||
'descr': _("Compressed Structured Vector Graphs (SVGZ)"),
|
||||
'mime' : "image/svgz",
|
||||
'class': GVSvgzDoc}]
|
||||
|
||||
FORMATS += [{ 'type' : "jpg",
|
||||
'ext' : "jpg",
|
||||
'descr': _("JPEG image"),
|
||||
'mime' : "image/jpeg",
|
||||
'class': GVJpegDoc }]
|
||||
FORMATS += [{'type' : "jpg",
|
||||
'ext' : "jpg",
|
||||
'descr': _("JPEG image"),
|
||||
'mime' : "image/jpeg",
|
||||
'class': GVJpegDoc}]
|
||||
|
||||
FORMATS += [{ 'type' : "gif",
|
||||
'ext' : "gif",
|
||||
'descr': _("GIF image"),
|
||||
'mime' : "image/gif",
|
||||
'class': GVGifDoc }]
|
||||
FORMATS += [{'type' : "gif",
|
||||
'ext' : "gif",
|
||||
'descr': _("GIF image"),
|
||||
'mime' : "image/gif",
|
||||
'class': GVGifDoc}]
|
||||
|
||||
FORMATS += [{ 'type' : "png",
|
||||
'ext' : "png",
|
||||
'descr': _("PNG image"),
|
||||
'mime' : "image/png",
|
||||
'class': GVPngDoc }]
|
||||
FORMATS += [{'type' : "png",
|
||||
'ext' : "png",
|
||||
'descr': _("PNG image"),
|
||||
'mime' : "image/png",
|
||||
'class': GVPngDoc}]
|
||||
|
||||
FORMATS += [{ 'type' : "dot",
|
||||
'ext' : "gv",
|
||||
'descr': _("Graphviz File"),
|
||||
'mime' : "text/x-graphviz",
|
||||
'class': GVDotDoc }]
|
||||
FORMATS += [{'type' : "dot",
|
||||
'ext' : "gv",
|
||||
'descr': _("Graphviz File"),
|
||||
'mime' : "text/x-graphviz",
|
||||
'class': GVDotDoc}]
|
||||
@@ -0,0 +1,660 @@
|
||||
#
|
||||
# Gramps - a GTK+/GNOME based genealogy program
|
||||
#
|
||||
# Copyright (C) 2017-2018 Nick Hall
|
||||
#
|
||||
# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
#
|
||||
""" LaTeX Genealogy Tree adapter for Trees """
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Standard Python modules
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
from abc import ABCMeta, abstractmethod
|
||||
import os
|
||||
import shutil
|
||||
import re
|
||||
from subprocess import Popen, PIPE
|
||||
from io import StringIO
|
||||
import tempfile
|
||||
import logging
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Gramps modules
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
from ...utils.file import search_for
|
||||
from ...lib import Person, EventType, EventRoleType, Date
|
||||
from ...display.place import displayer as _pd
|
||||
from ...utils.file import media_path_full
|
||||
from . import BaseDoc, PAPER_PORTRAIT
|
||||
from ..menu import NumberOption, TextOption, EnumeratedListOption
|
||||
from ...constfunc import win
|
||||
from ...config import config
|
||||
from ...const import GRAMPS_LOCALE as glocale
|
||||
_ = glocale.translation.gettext
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# set up logging
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
LOG = logging.getLogger(".treedoc")
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Private Constants
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
_DETAIL = [{'name': _("Full"), 'value': "full"},
|
||||
{'name': _("Medium"), 'value': "medium"},
|
||||
{'name': _("Short"), 'value': "short"}]
|
||||
|
||||
_MARRIAGE = [{'name': _("Default"), 'value': ""},
|
||||
{'name': _("Above"), 'value': "marriage above"},
|
||||
{'name': _("Below"), 'value': "marriage below"},
|
||||
{'name': _("Not shown"), 'value': "no marriage"}]
|
||||
|
||||
_COLOR = [{'name': _("None"), 'value': "none"},
|
||||
{'name': _("Default"), 'value': "default"},
|
||||
{'name': _("Preferences"), 'value': "preferences"}]
|
||||
|
||||
_TIMEFLOW = [{'name': _("Down (↓)"), 'value': ""},
|
||||
{'name': _("Up (↑)"), 'value': "up"},
|
||||
{'name': _("Right (→)"), 'value': "right"},
|
||||
{'name': _("Left (←)"), 'value': "left"}]
|
||||
|
||||
_EDGES = [{'name': _("Perpendicular"), 'value': ""},
|
||||
{'name': _("Rounded"), 'value': "rounded", },
|
||||
{'name': _("Swing"), 'value': "swing", },
|
||||
{'name': _("Mesh"), 'value': 'mesh'}]
|
||||
|
||||
_NOTELOC = [{'name': _("Top"), 'value': "t"},
|
||||
{'name': _("Bottom"), 'value': "b"}]
|
||||
|
||||
_NOTESIZE = [{'name': _("Tiny"), 'value': "tiny"},
|
||||
{'name': _("Script"), 'value': "scriptsize"},
|
||||
{'name': _("Footnote"), 'value': "footnotesize"},
|
||||
{'name': _("Small"), 'value': "small"},
|
||||
{'name': _("Normal"), 'value': "normalsize"},
|
||||
{'name': _("Large"), 'value': "large"},
|
||||
{'name': _("Very large"), 'value': "Large"},
|
||||
{'name': _("Extra large"), 'value': "LARGE"},
|
||||
{'name': _("Huge"), 'value': "huge"},
|
||||
{'name': _("Extra huge"), 'value': "Huge"}]
|
||||
|
||||
if win():
|
||||
_LATEX_FOUND = search_for("lualatex.exe")
|
||||
DETACHED_PROCESS = 8
|
||||
else:
|
||||
_LATEX_FOUND = search_for("lualatex")
|
||||
|
||||
def escape(text):
|
||||
lookup = {
|
||||
'&': '\\&',
|
||||
'%': '\\%',
|
||||
'$': '\\$',
|
||||
'#': '\\#',
|
||||
'_': '\\_',
|
||||
'{': '\\{',
|
||||
'}': '\\}',
|
||||
'~': '\\~{}',
|
||||
'^': '\\^{}',
|
||||
'\\': '\\textbackslash{}'
|
||||
}
|
||||
pattern = re.compile('|'.join([re.escape(key) for key in lookup.keys()]))
|
||||
return pattern.sub(lambda match: lookup[match.group(0)], text)
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
#
|
||||
# TreeOptions
|
||||
#
|
||||
#------------------------------------------------------------------------------
|
||||
class TreeOptions:
|
||||
"""
|
||||
Defines all of the controls necessary
|
||||
to configure the genealogy tree reports.
|
||||
"""
|
||||
def add_menu_options(self, menu):
|
||||
"""
|
||||
Add all graph related options to the menu.
|
||||
|
||||
:param menu: The menu the options should be added to.
|
||||
:type menu: :class:`.Menu`
|
||||
:return: nothing
|
||||
"""
|
||||
################################
|
||||
category = _("Node Options")
|
||||
################################
|
||||
|
||||
detail = EnumeratedListOption(_("Node detail"), "full")
|
||||
for item in _DETAIL:
|
||||
detail.add_item(item["value"], item["name"])
|
||||
detail.set_help(_("Detail of information to be shown in a node."))
|
||||
menu.add_option(category, "detail", detail)
|
||||
|
||||
marriage = EnumeratedListOption(_("Marriage"), "")
|
||||
for item in _MARRIAGE:
|
||||
marriage.add_item(item["value"], item["name"])
|
||||
marriage.set_help(_("Position of marriage information."))
|
||||
menu.add_option(category, "marriage", marriage)
|
||||
|
||||
nodesize = NumberOption(_("Node size"), 25, 5, 100, 5)
|
||||
nodesize.set_help(_("One dimension of a node, in mm. If the timeflow "
|
||||
"is up or down then this is the width, otherwise "
|
||||
"it is the height."))
|
||||
menu.add_option(category, "nodesize", nodesize)
|
||||
|
||||
levelsize = NumberOption(_("Level size"), 35, 5, 100, 5)
|
||||
levelsize.set_help(_("One dimension of a node, in mm. If the timeflow "
|
||||
"is up or down then this is the height, otherwise "
|
||||
"it is the width."))
|
||||
menu.add_option(category, "levelsize", levelsize)
|
||||
|
||||
nodecolor = EnumeratedListOption(_("Color"), "none")
|
||||
for item in _COLOR:
|
||||
nodecolor.add_item(item["value"], item["name"])
|
||||
nodecolor.set_help(_("Node color."))
|
||||
menu.add_option(category, "nodecolor", nodecolor)
|
||||
|
||||
################################
|
||||
category = _("Tree Options")
|
||||
################################
|
||||
|
||||
timeflow = EnumeratedListOption(_("Timeflow"), "")
|
||||
for item in _TIMEFLOW:
|
||||
timeflow.add_item(item["value"], item["name"])
|
||||
timeflow.set_help(_("Direction that the graph will grow over time."))
|
||||
menu.add_option(category, "timeflow", timeflow)
|
||||
|
||||
edges = EnumeratedListOption(_("Edge style"), "")
|
||||
for item in _EDGES:
|
||||
edges.add_item(item["value"], item["name"])
|
||||
edges.set_help(_("Style of the edges between nodes."))
|
||||
menu.add_option(category, "edges", edges)
|
||||
|
||||
leveldist = NumberOption(_("Level distance"), 5, 1, 20, 1)
|
||||
leveldist.set_help(_("The minimum amount of free space, in mm, "
|
||||
"between levels. For vertical graphs, this "
|
||||
"corresponds to spacing between rows. For "
|
||||
"horizontal graphs, this corresponds to spacing "
|
||||
"between columns."))
|
||||
menu.add_option(category, "leveldist", leveldist)
|
||||
|
||||
################################
|
||||
category = _("Note")
|
||||
################################
|
||||
|
||||
note = TextOption(_("Note to add to the tree"), [""])
|
||||
note.set_help(_("This text will be added to the tree."))
|
||||
menu.add_option(category, "note", note)
|
||||
|
||||
noteloc = EnumeratedListOption(_("Note location"), 't')
|
||||
for item in _NOTELOC:
|
||||
noteloc.add_item(item["value"], item["name"])
|
||||
noteloc.set_help(_("Whether note will appear on top "
|
||||
"or bottom of the page."))
|
||||
menu.add_option(category, "noteloc", noteloc)
|
||||
|
||||
notesize = EnumeratedListOption(_("Note size"), 'normalsize')
|
||||
for item in _NOTESIZE:
|
||||
notesize.add_item(item["value"], item["name"])
|
||||
notesize.set_help(_("The size of note text."))
|
||||
menu.add_option(category, "notesize", notesize)
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
#
|
||||
# TreeDoc
|
||||
#
|
||||
#------------------------------------------------------------------------------
|
||||
class TreeDoc(metaclass=ABCMeta):
|
||||
"""
|
||||
Abstract Interface for genealogy tree document generators. Output formats
|
||||
for genealogy tree reports must implement this interface to be used by the
|
||||
report system.
|
||||
"""
|
||||
@abstractmethod
|
||||
def start_tree(self, option_list):
|
||||
"""
|
||||
Write the start of a tree.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def end_tree(self):
|
||||
"""
|
||||
Write the end of a tree.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def start_subgraph(self, level, subgraph_type, family, option_list=None):
|
||||
"""
|
||||
Write the start of a subgraph.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def end_subgraph(self, level):
|
||||
"""
|
||||
Write the end of a subgraph.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def write_node(self, db, level, node_type, person, marriage_flag,
|
||||
option_list=None):
|
||||
"""
|
||||
Write the contents of a node.
|
||||
"""
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
#
|
||||
# TreeDocBase
|
||||
#
|
||||
#------------------------------------------------------------------------------
|
||||
class TreeDocBase(BaseDoc, TreeDoc):
|
||||
"""
|
||||
Base document generator for all Graphviz document generators. Classes that
|
||||
inherit from this class will only need to implement the close function.
|
||||
The close function will generate the actual file of the appropriate type.
|
||||
"""
|
||||
def __init__(self, options, paper_style):
|
||||
BaseDoc.__init__(self, None, paper_style)
|
||||
|
||||
self._filename = None
|
||||
self._tex = StringIO()
|
||||
self._paper = paper_style
|
||||
|
||||
get_option = options.menu.get_option_by_name
|
||||
|
||||
self.detail = get_option('detail').get_value()
|
||||
self.marriage = get_option('marriage').get_value()
|
||||
self.nodesize = get_option('nodesize').get_value()
|
||||
self.levelsize = get_option('levelsize').get_value()
|
||||
self.nodecolor = get_option('nodecolor').get_value()
|
||||
|
||||
self.timeflow = get_option('timeflow').get_value()
|
||||
self.edges = get_option('edges').get_value()
|
||||
self.leveldist = get_option('leveldist').get_value()
|
||||
|
||||
self.note = get_option('note').get_value()
|
||||
self.noteloc = get_option('noteloc').get_value()
|
||||
self.notesize = get_option('notesize').get_value()
|
||||
|
||||
def write_start(self):
|
||||
"""
|
||||
Write the start of the document.
|
||||
"""
|
||||
paper_size = self._paper.get_size()
|
||||
name = paper_size.get_name().lower()
|
||||
if name == 'custom size':
|
||||
width = str(paper_size.get_width())
|
||||
height = str(paper_size.get_width())
|
||||
paper = 'papersize={%scm,%scm}' % (width, height)
|
||||
elif name in ('a', 'b', 'c', 'd', 'e'):
|
||||
paper = 'ansi' + name + 'paper'
|
||||
else:
|
||||
paper = name + 'paper'
|
||||
|
||||
if self._paper.get_orientation() == PAPER_PORTRAIT:
|
||||
orientation = 'portrait'
|
||||
else:
|
||||
orientation = 'landscape'
|
||||
|
||||
lmargin = self._paper.get_left_margin()
|
||||
rmargin = self._paper.get_right_margin()
|
||||
tmargin = self._paper.get_top_margin()
|
||||
bmargin = self._paper.get_bottom_margin()
|
||||
if lmargin == rmargin == tmargin == bmargin:
|
||||
margin = 'margin=%scm'% lmargin
|
||||
else:
|
||||
if lmargin == rmargin:
|
||||
margin = 'hmargin=%scm' % lmargin
|
||||
else:
|
||||
margin = 'hmargin={%scm,%scm}' % (lmargin, rmargin)
|
||||
if tmargin == bmargin:
|
||||
margin += ',vmargin=%scm' % tmargin
|
||||
else:
|
||||
margin += ',vmargin={%scm,%scm}' % (tmargin, bmargin)
|
||||
|
||||
self.write(0, '\\documentclass[%s]{article}\n' % orientation)
|
||||
|
||||
self.write(0, '\\IfFileExists{libertine.sty}{\n')
|
||||
self.write(0, ' \\usepackage{libertine}\n')
|
||||
self.write(0, '}{}\n')
|
||||
|
||||
self.write(0, '\\usepackage[%s,%s]{geometry}\n' % (paper, margin))
|
||||
self.write(0, '\\usepackage[all]{genealogytree}\n')
|
||||
self.write(0, '\\usepackage{color}\n')
|
||||
self.write(0, '\\begin{document}\n')
|
||||
|
||||
if self.nodecolor == 'preferences':
|
||||
scheme = config.get('colors.scheme')
|
||||
male_bg = config.get('colors.male-dead')[scheme][1:]
|
||||
female_bg = config.get('colors.female-dead')[scheme][1:]
|
||||
neuter_bg = config.get('colors.unknown-dead')[scheme][1:]
|
||||
self.write(0, '\\definecolor{male-bg}{HTML}{%s}\n' % male_bg)
|
||||
self.write(0, '\\definecolor{female-bg}{HTML}{%s}\n' % female_bg)
|
||||
self.write(0, '\\definecolor{neuter-bg}{HTML}{%s}\n' % neuter_bg)
|
||||
|
||||
if ''.join(self.note) != '' and self.noteloc == 't':
|
||||
for line in self.note:
|
||||
self.write(0, '{\\%s %s}\\par\n' % (self.notesize, line))
|
||||
self.write(0, '\\bigskip\n')
|
||||
|
||||
self.write(0, '\\begin{tikzpicture}\n')
|
||||
|
||||
def start_tree(self, option_list):
|
||||
self.write(0, '\\genealogytree[\n')
|
||||
self.write(0, 'processing=database,\n')
|
||||
if self.marriage:
|
||||
info = self.detail + ' ' + self.marriage
|
||||
else:
|
||||
info = self.detail
|
||||
self.write(0, 'database format=%s,\n' % info)
|
||||
if self.timeflow:
|
||||
self.write(0, 'timeflow=%s,\n' % self.timeflow)
|
||||
if self.edges:
|
||||
self.write(0, 'edges=%s,\n' % self.edges)
|
||||
if self.leveldist != 5:
|
||||
self.write(0, 'level distance=%smm,\n' % self.leveldist)
|
||||
if self.nodesize != 25:
|
||||
self.write(0, 'node size=%smm,\n' % self.nodesize)
|
||||
if self.levelsize != 35:
|
||||
self.write(0, 'level size=%smm,\n' % self.levelsize)
|
||||
if self.nodecolor == 'none':
|
||||
self.write(0, 'tcbset={male/.style={},\n')
|
||||
self.write(0, ' female/.style={},\n')
|
||||
self.write(0, ' neuter/.style={}},\n')
|
||||
if self.nodecolor == 'preferences':
|
||||
self.write(0, 'tcbset={male/.style={colback=male-bg},\n')
|
||||
self.write(0, ' female/.style={colback=female-bg},\n')
|
||||
self.write(0, ' neuter/.style={colback=neuter-bg}},\n')
|
||||
|
||||
for option in option_list:
|
||||
self.write(0, '%s,\n' % option)
|
||||
|
||||
self.write(0, ']{\n')
|
||||
|
||||
def end_tree(self):
|
||||
self.write(0, '}\n')
|
||||
|
||||
def write_end(self):
|
||||
"""
|
||||
Write the end of the document.
|
||||
"""
|
||||
self.write(0, '\\end{tikzpicture}\n')
|
||||
|
||||
if ''.join(self.note) != '' and self.noteloc == 'b':
|
||||
self.write(0, '\\bigskip\n')
|
||||
for line in self.note:
|
||||
self.write(0, '\\par{\\%s %s}\n' % (self.notesize, line))
|
||||
|
||||
self.write(0, '\\end{document}\n')
|
||||
|
||||
def start_subgraph(self, level, subgraph_type, family, option_list=None):
|
||||
options = ['id=%s' % family.gramps_id]
|
||||
if option_list:
|
||||
options.extend(option_list)
|
||||
if subgraph_type == 'sandclock':
|
||||
self.write(level, 'sandclock{\n')
|
||||
else:
|
||||
self.write(level, '%s[%s]{\n' % (subgraph_type, ','.join(options)))
|
||||
|
||||
def end_subgraph(self, level):
|
||||
self.write(level, '}\n')
|
||||
|
||||
def write_node(self, db, level, node_type, person, marriage_flag,
|
||||
option_list=None):
|
||||
options = ['id=%s' % person.gramps_id]
|
||||
if option_list:
|
||||
options.extend(option_list)
|
||||
self.write(level, '%s[%s]{\n' % (node_type, ','.join(options)))
|
||||
if person.gender == Person.MALE:
|
||||
self.write(level+1, 'male,\n')
|
||||
elif person.gender == Person.FEMALE:
|
||||
self.write(level+1, 'female,\n')
|
||||
elif person.gender == Person.UNKNOWN:
|
||||
self.write(level+1, 'neuter,\n')
|
||||
name = person.get_primary_name()
|
||||
nick = name.get_nick_name()
|
||||
surn = name.get_surname()
|
||||
name_parts = [self.format_given_names(name),
|
||||
'\\nick{{{}}}'.format(escape(nick)) if nick else '',
|
||||
'\\surn{{{}}}'.format(escape(surn)) if surn else '']
|
||||
self.write(level+1, 'name = {{{}}},\n'.format(
|
||||
' '.join([e for e in name_parts if e])))
|
||||
for eventref in person.get_event_ref_list():
|
||||
if eventref.role == EventRoleType.PRIMARY:
|
||||
event = db.get_event_from_handle(eventref.ref)
|
||||
self.write_event(db, level+1, event)
|
||||
if marriage_flag:
|
||||
for handle in person.get_family_handle_list():
|
||||
family = db.get_family_from_handle(handle)
|
||||
for eventref in family.get_event_ref_list():
|
||||
if eventref.role == EventRoleType.FAMILY:
|
||||
event = db.get_event_from_handle(eventref.ref)
|
||||
self.write_event(db, level+1, event)
|
||||
for attr in person.get_attribute_list():
|
||||
if str(attr.get_type()) == 'Occupation':
|
||||
self.write(level+1, 'profession = {%s},\n' %
|
||||
escape(attr.get_value()))
|
||||
if str(attr.get_type()) == 'Comment':
|
||||
self.write(level+1, 'comment = {%s},\n' %
|
||||
escape(attr.get_value()))
|
||||
for mediaref in person.get_media_list():
|
||||
media = db.get_media_from_handle(mediaref.ref)
|
||||
path = media_path_full(db, media.get_path())
|
||||
if os.path.isfile(path):
|
||||
if win():
|
||||
path = path.replace('\\', '/')
|
||||
self.write(level+1, 'image = {%s},\n' % path)
|
||||
break # first image only
|
||||
self.write(level, '}\n')
|
||||
|
||||
def write_event(self, db, level, event):
|
||||
"""
|
||||
Write an event.
|
||||
"""
|
||||
modifier = None
|
||||
if event.type == EventType.BIRTH:
|
||||
event_type = 'birth'
|
||||
if 'died' in event.description.lower():
|
||||
modifier = 'died'
|
||||
if 'stillborn' in event.description.lower():
|
||||
modifier = 'stillborn'
|
||||
# modifier = 'out of wedlock'
|
||||
elif event.type == EventType.BAPTISM:
|
||||
event_type = 'baptism'
|
||||
elif event.type == EventType.ENGAGEMENT:
|
||||
event_type = 'engagement'
|
||||
elif event.type == EventType.MARRIAGE:
|
||||
event_type = 'marriage'
|
||||
elif event.type == EventType.DIVORCE:
|
||||
event_type = 'divorce'
|
||||
elif event.type == EventType.DEATH:
|
||||
event_type = 'death'
|
||||
elif event.type == EventType.BURIAL:
|
||||
event_type = 'burial'
|
||||
if 'killed' in event.description.lower():
|
||||
modifier = 'killed'
|
||||
elif event.type == EventType.CREMATION:
|
||||
event_type = 'burial'
|
||||
modifier = 'cremated'
|
||||
else:
|
||||
return
|
||||
|
||||
date = event.get_date_object()
|
||||
|
||||
if date.get_calendar() == Date.CAL_GREGORIAN:
|
||||
calendar = 'AD' # GR
|
||||
elif date.get_calendar() == Date.CAL_JULIAN:
|
||||
calendar = 'JU'
|
||||
else:
|
||||
calendar = ''
|
||||
|
||||
if date.get_modifier() == Date.MOD_ABOUT:
|
||||
calendar = 'ca' + calendar
|
||||
|
||||
date_str = self.format_iso(date.get_ymd(), calendar)
|
||||
if date.get_modifier() == Date.MOD_BEFORE:
|
||||
date_str = '/' + date_str
|
||||
elif date.get_modifier() == Date.MOD_AFTER:
|
||||
date_str = date_str + '/'
|
||||
elif date.is_compound():
|
||||
stop_date = self.format_iso(date.get_stop_ymd(), calendar)
|
||||
date_str = date_str + '/' + stop_date
|
||||
|
||||
place = escape(_pd.display_event(db, event))
|
||||
|
||||
if modifier:
|
||||
event_type += '+'
|
||||
self.write(level, '%s = {%s}{%s}{%s},\n' %
|
||||
(event_type, date_str, place, modifier))
|
||||
elif place == '':
|
||||
event_type += '-'
|
||||
self.write(level, '%s = {%s},\n' % (event_type, date_str))
|
||||
else:
|
||||
self.write(level, '%s = {%s}{%s},\n' %
|
||||
(event_type, date_str, place))
|
||||
|
||||
def format_given_names(self, name):
|
||||
"""
|
||||
Format given names.
|
||||
"""
|
||||
first = name.get_first_name()
|
||||
call = name.get_call_name()
|
||||
if call:
|
||||
if call in first:
|
||||
where = first.index(call)
|
||||
return '{before}\\pref{{{call}}}{after}'.format(
|
||||
before=escape(first[:where]),
|
||||
call=escape(call),
|
||||
after=escape(first[where+len(call):]))
|
||||
else:
|
||||
# ignore erroneous call name
|
||||
return escape(first)
|
||||
else:
|
||||
return escape(first)
|
||||
|
||||
def format_iso(self, date_tuple, calendar):
|
||||
"""
|
||||
Format an iso date.
|
||||
"""
|
||||
year, month, day = date_tuple
|
||||
if year == 0:
|
||||
iso_date = ''
|
||||
elif month == 0:
|
||||
iso_date = str(year)
|
||||
elif day == 0:
|
||||
iso_date = '%s-%s' % (year, month)
|
||||
else:
|
||||
iso_date = '%s-%s-%s' % (year, month, day)
|
||||
if calendar and calendar != 'AD':
|
||||
iso_date = '(%s)%s' % (calendar, iso_date)
|
||||
return iso_date
|
||||
|
||||
def write(self, level, text):
|
||||
"""
|
||||
Write indented text.
|
||||
"""
|
||||
self._tex.write(' '*level + text)
|
||||
|
||||
def open(self, filename):
|
||||
""" Implement TreeDocBase.open() """
|
||||
self._filename = os.path.normpath(os.path.abspath(filename))
|
||||
self.write_start()
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
This isn't useful by itself. Other classes need to override this and
|
||||
actually generate a file.
|
||||
"""
|
||||
self.write_end()
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
#
|
||||
# TreeTexDoc
|
||||
#
|
||||
#------------------------------------------------------------------------------
|
||||
class TreeTexDoc(TreeDocBase):
|
||||
"""
|
||||
TreeTexDoc implementation that generates a .tex file.
|
||||
"""
|
||||
|
||||
def close(self):
|
||||
""" Implements TreeDocBase.close() """
|
||||
TreeDocBase.close(self)
|
||||
|
||||
# Make sure the extension is correct
|
||||
if self._filename[-4:] != ".tex":
|
||||
self._filename += ".tex"
|
||||
|
||||
with open(self._filename, 'w', encoding='utf-8') as texfile:
|
||||
texfile.write(self._tex.getvalue())
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
#
|
||||
# TreePdfDoc
|
||||
#
|
||||
#------------------------------------------------------------------------------
|
||||
class TreePdfDoc(TreeDocBase):
|
||||
"""
|
||||
TreePdfDoc implementation that generates a .pdf file.
|
||||
"""
|
||||
|
||||
def close(self):
|
||||
""" Implements TreeDocBase.close() """
|
||||
TreeDocBase.close(self)
|
||||
|
||||
# Make sure the extension is correct
|
||||
if self._filename[-4:] != ".pdf":
|
||||
self._filename += ".pdf"
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
basename = os.path.basename(self._filename)
|
||||
args = ['lualatex', '-output-directory', tmpdir,
|
||||
'-jobname', basename[:-4]]
|
||||
if win():
|
||||
proc = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE,
|
||||
creationflags=DETACHED_PROCESS)
|
||||
else:
|
||||
proc = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE)
|
||||
proc.communicate(input=self._tex.getvalue().encode('utf-8'))
|
||||
shutil.copy(os.path.join(tmpdir, basename), self._filename)
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
#
|
||||
# Various Genealogy Tree formats.
|
||||
#
|
||||
#------------------------------------------------------------------------------
|
||||
FORMATS = []
|
||||
|
||||
if _LATEX_FOUND:
|
||||
FORMATS += [{'type' : "pdf",
|
||||
'ext' : "pdf",
|
||||
'descr': _("PDF"),
|
||||
'mime' : "application/pdf",
|
||||
'class': TreePdfDoc}]
|
||||
|
||||
FORMATS += [{'type' : "tex",
|
||||
'ext' : "tex",
|
||||
'descr': _("LaTeX File"),
|
||||
'mime' : "application/x-latex",
|
||||
'class': TreeTexDoc}]
|
||||
@@ -495,14 +495,14 @@ class BookList:
|
||||
"""
|
||||
Saves the current BookList to the associated file.
|
||||
"""
|
||||
with open(self.file, "w") as b_f:
|
||||
with open(self.file, "w", encoding="utf-8") as b_f:
|
||||
b_f.write("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n")
|
||||
b_f.write('<booklist>\n')
|
||||
for name in sorted(self.bookmap): # enable a diff of archived copies
|
||||
book = self.get_book(name)
|
||||
dbname = book.get_dbname()
|
||||
dbname = escape(book.get_dbname())
|
||||
b_f.write(' <book name="%s" database="%s">'
|
||||
'\n' % (name, dbname))
|
||||
'\n' % (escape(name), dbname))
|
||||
for item in book.get_item_list():
|
||||
b_f.write(' <item name="%s" '
|
||||
'trans_name="%s">\n' % (
|
||||
@@ -566,7 +566,7 @@ class BookList:
|
||||
'\n' % book.get_format_name())
|
||||
if book.get_output():
|
||||
b_f.write(' <output name="%s"/>'
|
||||
'\n' % book.get_output())
|
||||
'\n' % escape(book.get_output()))
|
||||
b_f.write(' </book>\n')
|
||||
|
||||
b_f.write('</booklist>\n')
|
||||
@@ -578,8 +578,15 @@ class BookList:
|
||||
try:
|
||||
parser = make_parser()
|
||||
parser.setContentHandler(BookParser(self, self.dbase))
|
||||
with open(self.file) as the_file:
|
||||
parser.parse(the_file)
|
||||
# bug 10387; XML should be utf8, but was not previously saved
|
||||
# that way. So try to read utf8, if fails, try with system
|
||||
# encoding. Only an issue on non-utf8 systems.
|
||||
try:
|
||||
with open(self.file, encoding="utf-8") as the_file:
|
||||
parser.parse(the_file)
|
||||
except UnicodeDecodeError:
|
||||
with open(self.file) as the_file:
|
||||
parser.parse(the_file)
|
||||
except (IOError, OSError, ValueError, SAXParseException, KeyError,
|
||||
AttributeError):
|
||||
LOG.debug("Failed to parse book list", exc_info=True)
|
||||
|
||||
@@ -39,7 +39,7 @@ import os
|
||||
|
||||
# Report categories
|
||||
from .. import (CATEGORY_TEXT, CATEGORY_DRAW, CATEGORY_CODE, CATEGORY_WEB,
|
||||
CATEGORY_BOOK, CATEGORY_GRAPHVIZ)
|
||||
CATEGORY_BOOK, CATEGORY_GRAPHVIZ, CATEGORY_TREE)
|
||||
|
||||
standalone_categories = {
|
||||
CATEGORY_TEXT : ("RepText", _("Text Reports")),
|
||||
@@ -48,6 +48,7 @@ standalone_categories = {
|
||||
CATEGORY_WEB : ("RepWeb", _("Web Pages")),
|
||||
CATEGORY_BOOK : ("RepBook", _("Books")),
|
||||
CATEGORY_GRAPHVIZ : ("Graphs", _("Graphs")),
|
||||
CATEGORY_TREE : ("Trees", _("Trees")),
|
||||
}
|
||||
book_categories = {
|
||||
CATEGORY_TEXT : _("Text"),
|
||||
|
||||
@@ -32,6 +32,7 @@ from ...config import config
|
||||
from ...datehandler import get_date_formats, LANG_TO_DISPLAY, main_locale
|
||||
from ...display.name import displayer as global_name_display
|
||||
from ...lib.date import Today
|
||||
from ...display.place import displayer as _pd
|
||||
from ..menu import EnumeratedListOption, BooleanOption, NumberOption
|
||||
from ...proxy import PrivateProxyDb, LivingProxyDb
|
||||
from ...utils.grampslocale import GrampsLocale
|
||||
@@ -327,3 +328,16 @@ def add_gramps_id_option(menu, category, ownline=False):
|
||||
include_id.add_item(1, _('Include'))
|
||||
include_id.set_help(_("Whether to include Gramps IDs"))
|
||||
menu.add_option(category, 'inc_id', include_id)
|
||||
|
||||
def add_place_format_option(menu, category):
|
||||
"""
|
||||
Insert an option for changing the report's place format to a
|
||||
report-specific format instead of the user's Edit=>Preferences choice
|
||||
"""
|
||||
place_format = EnumeratedListOption(_("Place format"), None)
|
||||
place_format.add_item(None, _("Default"))
|
||||
for number, fmt in enumerate(_pd.get_formats()):
|
||||
place_format.add_item(number, fmt.name)
|
||||
place_format.set_help(_("Select the format to display places"))
|
||||
menu.add_option(category, "place_format", place_format)
|
||||
return place_format
|
||||
@@ -40,7 +40,13 @@ class CacheProxyDb:
|
||||
proxies.
|
||||
"""
|
||||
self.db = database
|
||||
self.clear_cache()
|
||||
# Memory allocation is power of 2 where slots has to fit.
|
||||
# LRU uses one extra slot in its work, so set max to 2^17-1
|
||||
# otherwise we are just wasting memory
|
||||
self.cache_handle = LRU(131071)
|
||||
|
||||
def __del__(self):
|
||||
self.cache_handle.clear()
|
||||
|
||||
def __getattr__(self, attr):
|
||||
"""
|
||||
@@ -57,7 +63,7 @@ class CacheProxyDb:
|
||||
if handle:
|
||||
del self.cache_handle[handle]
|
||||
else:
|
||||
self.cache_handle = LRU(100000)
|
||||
self.cache_handle.clear()
|
||||
|
||||
def get_person_from_handle(self, handle):
|
||||
"""
|
||||
@@ -104,15 +110,6 @@ class CacheProxyDb:
|
||||
self.cache_handle[handle] = self.db.get_place_from_handle(handle)
|
||||
return self.cache_handle[handle]
|
||||
|
||||
def get_place_from_handle(self, handle):
|
||||
"""
|
||||
Gets item from cache if it exists. Converts
|
||||
handles to string, for uniformity.
|
||||
"""
|
||||
if handle not in self.cache_handle:
|
||||
self.cache_handle[handle] = self.db.get_place_from_handle(handle)
|
||||
return self.cache_handle[handle]
|
||||
|
||||
def get_citation_from_handle(self, handle):
|
||||
"""
|
||||
Gets item from cache if it exists. Converts
|
||||
|
||||
@@ -936,12 +936,6 @@ class ProxyDbBase(DbReadBase):
|
||||
"""returns the default media path of the database"""
|
||||
return self.db.get_mediapath()
|
||||
|
||||
def get_gramps_ids(self, obj_key):
|
||||
return self.db.get_gramps_ids(obj_key)
|
||||
|
||||
def has_gramps_id(self, obj_key, gramps_id):
|
||||
return self.db.has_gramps_id(obj_key, gramps_id)
|
||||
|
||||
def get_bookmarks(self):
|
||||
"""returns the list of Person handles in the bookmarks"""
|
||||
return self.bookmarks
|
||||
|
||||
@@ -961,10 +961,10 @@ class SimpleAccess:
|
||||
:param prop: "gramps_id", or "handle"
|
||||
:param value: gramps_id or handle.
|
||||
"""
|
||||
if object_class in self.dbase.get_table_names():
|
||||
func = self.dbase.method('get_%s_from_%s', object_class, prop)
|
||||
if func:
|
||||
try:
|
||||
obj = self.dbase.get_table_metadata(
|
||||
object_class)[prop + "_func"](value)
|
||||
obj = func(value)
|
||||
except HandleError:
|
||||
# Deals with deleted objects referenced in Note Links
|
||||
obj = None
|
||||
@@ -1021,8 +1021,9 @@ class SimpleAccess:
|
||||
Given a object, return a string describing the object.
|
||||
"""
|
||||
if prop and value:
|
||||
if self.dbase.get_table_metadata(obj):
|
||||
obj = self.dbase.get_table_metadata(obj)[prop + "_func"](value)
|
||||
func = self.dbase.method('get_%s_from_%s', object_class, prop)
|
||||
if func:
|
||||
obj = func(value)
|
||||
if isinstance(obj, Person):
|
||||
return "%s [%s]" % (self.name(obj),
|
||||
self.gid(obj))
|
||||
@@ -1064,6 +1065,6 @@ class SimpleAccess:
|
||||
:param prop: "gramps_id", or "handle"
|
||||
:param value: gramps_id or handle.
|
||||
"""
|
||||
if object_class in self.dbase.get_table_names():
|
||||
return self.dbase.get_table_metadata(object_class) \
|
||||
[prop + "_func"](value)
|
||||
func = self.dbase.method('get_%s_from_%s', object_class, prop)
|
||||
if func:
|
||||
return func(value)
|
||||
@@ -31,6 +31,7 @@ import csv
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
from .tabbeddoc import *
|
||||
from ...constfunc import win
|
||||
|
||||
class CSVTab(TabbedDoc):
|
||||
|
||||
@@ -48,7 +49,8 @@ class CSVTab(TabbedDoc):
|
||||
else:
|
||||
self.filename = filename
|
||||
|
||||
self.f = open(self.filename, "w")
|
||||
self.f = open(self.filename, "w",
|
||||
encoding='utf_8_sig' if win() else 'utf_8')
|
||||
self.writer = csv.writer(self.f)
|
||||
|
||||
def close(self):
|
||||
|
||||
@@ -61,7 +61,7 @@ def find_file( filename):
|
||||
try:
|
||||
if os.path.isfile(filename):
|
||||
return(filename)
|
||||
except UnicodeError:
|
||||
except UnicodeError as err:
|
||||
LOG.error("Filename %s raised a Unicode Error %s.", repr(filename), err)
|
||||
|
||||
LOG.debug("Filename %s not found.", repr(filename))
|
||||
@@ -228,6 +228,24 @@ def search_for(name):
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
def where_is(name):
|
||||
""" This command is similar to the Linux "whereis -b file" command.
|
||||
It looks for an executable file (name) in the PATH python is using, as
|
||||
well as several likely other paths. It returns the first file found,
|
||||
or an empty string if not found.
|
||||
"""
|
||||
paths = set(os.environ['PATH'].split(os.pathsep))
|
||||
if not win():
|
||||
paths.update(("/bin", "/usr/bin", "/usr/local/bin", "/opt/local/bin",
|
||||
"/opt/bin"))
|
||||
for i in paths:
|
||||
fname = os.path.join(i, name)
|
||||
if os.access(fname, os.X_OK) and not os.path.isdir(fname):
|
||||
return fname
|
||||
return ""
|
||||
|
||||
|
||||
def create_checksum(full_path):
|
||||
"""
|
||||
Create a md5 hash for the given file.
|
||||
|
||||
@@ -60,7 +60,6 @@ class FileTest(unittest.TestCase):
|
||||
# Create database
|
||||
db = make_database("bsddb")
|
||||
path = get_empty_tempdir("utils_file_test")
|
||||
db.write_version(path)
|
||||
db.load(path)
|
||||
|
||||
# Test without db.mediapath set
|
||||
|
||||
@@ -117,6 +117,7 @@ def make_unknown(class_arg, explanation, class_func, commit_func, transaction,
|
||||
obj.set_type(EventType.UNKNOWN)
|
||||
elif isinstance(obj, Place):
|
||||
obj.set_title(_('Unknown'))
|
||||
obj.name.set_value(_('Unknown'))
|
||||
elif isinstance(obj, Source):
|
||||
obj.set_title(_('Unknown'))
|
||||
elif isinstance(obj, Citation):
|
||||
|
||||
+4
-5
@@ -399,11 +399,10 @@ def show_settings():
|
||||
print(' version : %s' % bsddb_str)
|
||||
print(' db version : %s' % bsddb_db_str)
|
||||
print(' location : %s' % bsddb_location_str)
|
||||
if __debug__:
|
||||
print(' sqlite3 :')
|
||||
print(' version : %s' % sqlite3_version_str)
|
||||
print(' py version : %s' % sqlite3_py_version_str)
|
||||
print(' location : %s' % sqlite3_location_str)
|
||||
print(' sqlite3 :')
|
||||
print(' version : %s' % sqlite3_version_str)
|
||||
print(' py version : %s' % sqlite3_py_version_str)
|
||||
print(' location : %s' % sqlite3_location_str)
|
||||
print('')
|
||||
|
||||
def run():
|
||||
|
||||
@@ -130,10 +130,8 @@ class GrampsAboutDialog(Gtk.AboutDialog):
|
||||
if hasattr(os, "uname"):
|
||||
distro = "\n" + _("Distribution: %s") % ellipses(os.uname()[2])
|
||||
|
||||
sqlite = ''
|
||||
if __debug__:
|
||||
sqlite = "sqlite" + COLON + " %s (%s)\n" % (sqlite3_version_str,
|
||||
sqlite3_py_version_str)
|
||||
sqlite = "sqlite" + COLON + " %s (%s)\n" % (sqlite3_version_str,
|
||||
sqlite3_py_version_str)
|
||||
|
||||
return (("\n\n" +
|
||||
"GRAMPS" + COLON + " %s \n" +
|
||||
|
||||
@@ -60,7 +60,7 @@ from gramps.gen.constfunc import mac
|
||||
from .glade import Glade
|
||||
from .ddtargets import DdTargets
|
||||
from .makefilter import make_filter
|
||||
from .utils import is_right_click
|
||||
from .utils import is_right_click, get_primary_mask
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
@@ -1573,7 +1573,7 @@ class MultiTreeView(Gtk.TreeView):
|
||||
o = store.get_value(node, 1)
|
||||
if o._objclass == objclass:
|
||||
my_handle = o._handle
|
||||
obj = self.dbstate.db.get_table_metadata(objclass)["handle_func"](my_handle)
|
||||
obj = self.dbstate.db.method('get_%s_from_handle', objclass)(my_handle)
|
||||
if obj:
|
||||
gids.add(obj.gramps_id)
|
||||
menu_item = Gtk.MenuItem(label=_("the object|Create Filter from %s selected...") % glocale.trans_objclass(objclass))
|
||||
@@ -1598,7 +1598,7 @@ class MultiTreeView(Gtk.TreeView):
|
||||
# otherwise:
|
||||
if (target
|
||||
and event.type == Gdk.EventType.BUTTON_PRESS
|
||||
and not (event.get_state() & (Gdk.ModifierType.CONTROL_MASK|Gdk.ModifierType.SHIFT_MASK))
|
||||
and not (event.get_state() & get_primary_mask(Gdk.ModifierType.SHIFT_MASK))
|
||||
and self.get_selection().path_is_selected(target[0])):
|
||||
# disable selection
|
||||
self.get_selection().set_select_function(lambda *ignore: False, None)
|
||||
|
||||
+175
-105
@@ -53,6 +53,7 @@ from gramps.gen.const import HOME_DIR, URL_WIKISTRING
|
||||
from gramps.gen.datehandler import get_date_formats
|
||||
from gramps.gen.display.name import displayer as _nd
|
||||
from gramps.gen.display.name import NameDisplayError
|
||||
from gramps.gen.display.place import displayer as _pd
|
||||
from gramps.gen.utils.alive import update_constants
|
||||
from gramps.gen.utils.file import media_path
|
||||
from gramps.gen.utils.keyword import (get_keywords, get_translation_from_keyword,
|
||||
@@ -62,6 +63,7 @@ from gramps.gen.lib import Name, Surname, NameOriginType
|
||||
from .managedwindow import ManagedWindow
|
||||
from .widgets import MarkupLabel, BasicLabel
|
||||
from .dialog import ErrorDialog, QuestionDialog2, OkDialog
|
||||
from .editors.editplaceformat import EditPlaceFormat
|
||||
from .glade import Glade
|
||||
from gramps.gen.plug.utils import available_updates
|
||||
from .plug import PluginWindows
|
||||
@@ -249,13 +251,23 @@ class ConfigureDialog(ManagedWindow):
|
||||
"""
|
||||
self.__config.set(constant, obj.get_text())
|
||||
|
||||
def update_color(self, obj, constant, color_hex_label):
|
||||
def update_color(self, obj, pspec, constant, color_hex_label):
|
||||
"""
|
||||
Called on changing some color.
|
||||
Either on programmatically color change.
|
||||
"""
|
||||
rgba = obj.get_rgba()
|
||||
hexval = "#%02x%02x%02x" % (int(rgba.red * 255),
|
||||
int(rgba.green * 255),
|
||||
int(rgba.blue * 255))
|
||||
color_hex_label.set_text(hexval)
|
||||
self.__config.set(constant, hexval)
|
||||
colors = self.__config.get(constant)
|
||||
if isinstance(colors, list):
|
||||
scheme = self.__config.get('colors.scheme')
|
||||
colors[scheme] = hexval
|
||||
self.__config.set(constant, colors)
|
||||
else:
|
||||
self.__config.set(constant, hexval)
|
||||
|
||||
def update_checkbox(self, obj, constant, config=None):
|
||||
if not config:
|
||||
@@ -383,15 +395,24 @@ class ConfigureDialog(ManagedWindow):
|
||||
grid.attach(entry, col_attach+1, index, 1, 1)
|
||||
|
||||
def add_color(self, grid, label, index, constant, config=None, col=0):
|
||||
"""
|
||||
Add color chooser widget with label to the grid.
|
||||
"""
|
||||
if not config:
|
||||
config = self.__config
|
||||
lwidget = BasicLabel(_("%s: ") % label) # needed for French, else ignore
|
||||
hexval = config.get(constant)
|
||||
colors = config.get(constant)
|
||||
if isinstance(colors, list):
|
||||
scheme = config.get('colors.scheme')
|
||||
hexval = colors[scheme]
|
||||
else:
|
||||
hexval = colors
|
||||
color = Gdk.color_parse(hexval)
|
||||
entry = Gtk.ColorButton(color=color)
|
||||
color_hex_label = BasicLabel(hexval)
|
||||
color_hex_label.set_hexpand(True)
|
||||
entry.connect('color-set', self.update_color, constant, color_hex_label)
|
||||
entry.connect('notify::color', self.update_color, constant,
|
||||
color_hex_label)
|
||||
grid.attach(lwidget, col, index, 1, 1)
|
||||
grid.attach(entry, col+1, index, 1, 1)
|
||||
grid.attach(color_hex_label, col+2, index, 1, 1)
|
||||
@@ -492,7 +513,6 @@ class GrampsPreferences(ConfigureDialog):
|
||||
self.add_behavior_panel,
|
||||
self.add_famtree_panel,
|
||||
self.add_formats_panel,
|
||||
self.add_places_panel,
|
||||
self.add_text_panel,
|
||||
self.add_prefix_panel,
|
||||
self.add_date_panel,
|
||||
@@ -554,7 +574,7 @@ class GrampsPreferences(ConfigureDialog):
|
||||
|
||||
def add_color_panel(self, configdialog):
|
||||
"""
|
||||
Add the tab to set defaults colors for graph boxes
|
||||
Add the tab to set defaults colors for graph boxes.
|
||||
"""
|
||||
grid = Gtk.Grid()
|
||||
grid.set_border_width(12)
|
||||
@@ -562,40 +582,62 @@ class GrampsPreferences(ConfigureDialog):
|
||||
grid.set_row_spacing(6)
|
||||
self.add_text(grid, _('Set the colors used for boxes in the graphical views'),
|
||||
0, line_wrap=False)
|
||||
self.add_color(grid, _('Gender Male Alive'), 1,
|
||||
'preferences.color-gender-male-alive')
|
||||
self.add_color(grid, _('Border Male Alive'), 2,
|
||||
'preferences.bordercolor-gender-male-alive')
|
||||
self.add_color(grid, _('Gender Male Death'), 3,
|
||||
'preferences.color-gender-male-death')
|
||||
self.add_color(grid, _('Border Male Death'), 4,
|
||||
'preferences.bordercolor-gender-male-death')
|
||||
self.add_color(grid, _('Gender Female Alive'), 1,
|
||||
'preferences.color-gender-female-alive', col=4)
|
||||
self.add_color(grid, _('Border Female Alive'), 2,
|
||||
'preferences.bordercolor-gender-female-alive', col=4)
|
||||
self.add_color(grid, _('Gender Female Death'), 3,
|
||||
'preferences.color-gender-female-death', col=4)
|
||||
self.add_color(grid, _('Border Female Death'), 4,
|
||||
'preferences.bordercolor-gender-female-death', col=4)
|
||||
## self.add_color(grid, _('Gender Other Alive'), 5,
|
||||
## 'preferences.color-gender-other-alive')
|
||||
## self.add_color(grid, _('Border Other Alive'), 6,
|
||||
## 'preferences.bordercolor-gender-other-alive')
|
||||
## self.add_color(grid, _('Gender Other Death'), 7,
|
||||
## 'preferences.color-gender-other-death')
|
||||
## self.add_color(grid, _('Border Other Death'), 8,
|
||||
## 'preferences.bordercolor-gender-other-death')
|
||||
self.add_color(grid, _('Gender Unknown Alive'), 5,
|
||||
'preferences.color-gender-unknown-alive', col=4)
|
||||
self.add_color(grid, _('Border Unknown Alive'), 6,
|
||||
'preferences.bordercolor-gender-unknown-alive', col=4)
|
||||
self.add_color(grid, _('Gender Unknown Death'), 7,
|
||||
'preferences.color-gender-unknown-death', col=4)
|
||||
self.add_color(grid, _('Border Unknown Death'), 8,
|
||||
'preferences.bordercolor-gender-unknown-death', col=4)
|
||||
|
||||
hbox = Gtk.Box(spacing=12)
|
||||
self.color_scheme_box = Gtk.ComboBoxText()
|
||||
formats = [_("Light colors"),
|
||||
_("Dark colors"),]
|
||||
list(map(self.color_scheme_box.append_text, formats))
|
||||
scheme = config.get('colors.scheme')
|
||||
self.color_scheme_box.set_active(scheme)
|
||||
self.color_scheme_box.connect('changed', self.color_scheme_changed)
|
||||
lwidget = BasicLabel(_("%s: ") % _('Color scheme'))
|
||||
hbox.pack_start(lwidget, False, False, 0)
|
||||
hbox.pack_start(self.color_scheme_box, False, False, 0)
|
||||
|
||||
restore_btn = Gtk.Button(_('Restore to defaults'))
|
||||
restore_btn.connect('clicked', self.restore_colors)
|
||||
hbox.pack_start(restore_btn, False, False, 0)
|
||||
grid.attach(hbox, 1, 1, 6, 1)
|
||||
|
||||
color_list = [
|
||||
(_('Male Alive'), 'male-alive', 2, 0),
|
||||
(_('Male Dead'), 'male-dead', 4, 0),
|
||||
(_('Female Alive'), 'female-alive', 2, 4),
|
||||
(_('Female Dead'), 'female-dead', 4, 4),
|
||||
(_('Unknown Alive'), 'unknown-alive', 6, 4),
|
||||
(_('Unknown Dead'), 'unknown-dead', 8, 4),
|
||||
(_('Family Node'), 'family', 7, 0),
|
||||
(_('Family Divorced'), 'family-divorced', 9, 0),
|
||||
(_('Home Person'), 'home-person', 6, 0),
|
||||
(_('Border Male Alive'), 'border-male-alive', 3, 0),
|
||||
(_('Border Male Dead'), 'border-male-dead', 5, 0),
|
||||
(_('Border Female Alive'), 'border-female-alive', 3, 4),
|
||||
(_('Border Female Dead'), 'border-female-dead', 5, 4),
|
||||
(_('Border Unknown Alive'), 'border-unknown-alive', 7, 4),
|
||||
(_('Border Unknown Dead'), 'border-unknown-dead', 9, 4),
|
||||
(_('Border Family'), 'border-family', 8, 0),
|
||||
(_('Border Family Divorced'), 'border-family-divorced', 10, 0),
|
||||
]
|
||||
|
||||
self.colors = {}
|
||||
for color in color_list:
|
||||
pref_name = 'colors.' + color[1]
|
||||
self.colors[pref_name] = self.add_color(grid, color[0], color[2],
|
||||
pref_name, col=color[3])
|
||||
return _('Colors'), grid
|
||||
|
||||
def restore_colors(self, widget=None):
|
||||
"""
|
||||
Restore colors of selected scheme to default.
|
||||
"""
|
||||
scheme = config.get('colors.scheme')
|
||||
for key, widget in self.colors.items():
|
||||
color = Gdk.RGBA()
|
||||
hexval = config.get_default(key)[scheme]
|
||||
Gdk.RGBA.parse(color, hexval)
|
||||
widget.set_rgba(color)
|
||||
|
||||
def add_advanced_panel(self, configdialog):
|
||||
grid = Gtk.Grid()
|
||||
grid.set_border_width(12)
|
||||
@@ -890,6 +932,13 @@ class GrampsPreferences(ConfigureDialog):
|
||||
_nd.set_default_format(new_idx)
|
||||
self.uistate.emit('nameformat-changed')
|
||||
|
||||
def cb_place_fmt_changed(self, obj):
|
||||
"""
|
||||
Called when the place format is changed.
|
||||
"""
|
||||
config.set('preferences.place-format', obj.get_active())
|
||||
self.uistate.emit('placeformat-changed')
|
||||
|
||||
def cb_pa_sur_changed(self,*args):
|
||||
"""
|
||||
checkbox patronymic as surname changed, propagate to namedisplayer
|
||||
@@ -1011,6 +1060,34 @@ class GrampsPreferences(ConfigureDialog):
|
||||
grid.attach(obox, 1, row, 2, 1)
|
||||
row += 1
|
||||
|
||||
# Place format:
|
||||
self.pformat = Gtk.ComboBox()
|
||||
renderer = Gtk.CellRendererText()
|
||||
self.pformat.pack_start(renderer, True)
|
||||
self.pformat.add_attribute(renderer, "text", 0)
|
||||
self.cb_place_fmt_rebuild()
|
||||
active = config.get('preferences.place-format')
|
||||
self.pformat.set_active(active)
|
||||
self.pformat.connect('changed', self.cb_place_fmt_changed)
|
||||
lwidget = BasicLabel(_("%s: ") % _('Place format'))
|
||||
lwidget.set_use_underline(True)
|
||||
lwidget.set_mnemonic_widget(obox)
|
||||
hbox = Gtk.Box()
|
||||
self.fmt_btn = Gtk.Button(label=("%s..." % _('Edit')))
|
||||
self.fmt_btn.connect('clicked', self.cb_place_fmt_dialog)
|
||||
hbox.pack_start(self.pformat, True, True, 0)
|
||||
hbox.pack_start(self.fmt_btn, False, False, 0)
|
||||
grid.attach(lwidget, 0, row, 1, 1)
|
||||
grid.attach(hbox, 1, row, 2, 1)
|
||||
row += 1
|
||||
|
||||
auto = self.add_checkbox(grid,
|
||||
_("Enable automatic place title generation"),
|
||||
row, 'preferences.place-auto',
|
||||
extra_callback=self.auto_title_changed)
|
||||
self.auto_title_changed(auto)
|
||||
row += 1
|
||||
|
||||
# Age precision:
|
||||
# precision=1 for "year", 2: "year, month" or 3: "year, month, days"
|
||||
obox = Gtk.ComboBoxText()
|
||||
@@ -1110,66 +1187,13 @@ class GrampsPreferences(ConfigureDialog):
|
||||
row += 1
|
||||
return _('Display'), grid
|
||||
|
||||
def add_places_panel(self, configdialog):
|
||||
grid = Gtk.Grid()
|
||||
grid.set_border_width(12)
|
||||
grid.set_column_spacing(6)
|
||||
grid.set_row_spacing(6)
|
||||
|
||||
auto = self.add_checkbox(grid,
|
||||
_("Enable automatic place title generation"),
|
||||
0, 'preferences.place-auto',
|
||||
extra_callback=self.auto_title_changed)
|
||||
|
||||
row = 0
|
||||
grid2 = Gtk.Grid()
|
||||
grid2.set_border_width(12)
|
||||
grid2.set_column_spacing(6)
|
||||
grid2.set_row_spacing(6)
|
||||
grid.attach(grid2, 1, 1, 1, 1)
|
||||
|
||||
self.place_widgets = []
|
||||
cbox = self.add_checkbox(grid2, _("Suppress comma after house number"),
|
||||
row, 'preferences.place-number', start=0)
|
||||
self.place_widgets.append(cbox)
|
||||
row += 1
|
||||
|
||||
cbox = self.add_checkbox(grid2, _("Reverse display order"),
|
||||
row, 'preferences.place-reverse', start=0)
|
||||
self.place_widgets.append(cbox)
|
||||
row += 1
|
||||
|
||||
# Place restriction
|
||||
obox = Gtk.ComboBoxText()
|
||||
formats = [_("Full place name"),
|
||||
_("-> Hamlet/Village/Town/City"),
|
||||
_("Hamlet/Village/Town/City ->")]
|
||||
list(map(obox.append_text, formats))
|
||||
active = config.get('preferences.place-restrict')
|
||||
obox.set_active(active)
|
||||
obox.connect('changed', self.place_restrict_changed)
|
||||
lwidget = BasicLabel(_("%s: ") % _('Restrict'))
|
||||
grid2.attach(lwidget, 0, row, 1, 1)
|
||||
grid2.attach(obox, 1, row, 2, 1)
|
||||
self.place_widgets.append(obox)
|
||||
row += 1
|
||||
|
||||
entry = self.add_entry(grid2, _("Language"),
|
||||
row, 'preferences.place-lang')
|
||||
self.place_widgets.append(entry)
|
||||
row += 1
|
||||
|
||||
self.auto_title_changed(auto)
|
||||
|
||||
return _('Places'), grid
|
||||
|
||||
def auto_title_changed(self, obj):
|
||||
"""
|
||||
Update sensitivity of place configuration widgets.
|
||||
Update sensitivity of place format widget.
|
||||
"""
|
||||
active = obj.get_active()
|
||||
for widget in self.place_widgets:
|
||||
widget.set_sensitive(active)
|
||||
active = config.get('preferences.place-auto')
|
||||
self.pformat.set_sensitive(active)
|
||||
self.fmt_btn.set_sensitive(active)
|
||||
|
||||
def add_text_panel(self, configdialog):
|
||||
row = 0
|
||||
@@ -1205,6 +1229,35 @@ class GrampsPreferences(ConfigureDialog):
|
||||
self.old_format = the_list.get_value(the_iter, COL_FMT)
|
||||
win = DisplayNameEditor(self.uistate, self.dbstate, self.track, self)
|
||||
|
||||
def color_scheme_changed(self, obj):
|
||||
"""
|
||||
Called on swiching color scheme.
|
||||
"""
|
||||
scheme = obj.get_active()
|
||||
config.set('colors.scheme', scheme)
|
||||
for key, widget in self.colors.items():
|
||||
color = Gdk.RGBA()
|
||||
hexval = config.get(key)[scheme]
|
||||
Gdk.RGBA.parse(color, hexval)
|
||||
widget.set_rgba(color)
|
||||
|
||||
def cb_place_fmt_dialog(self, button):
|
||||
"""
|
||||
Called to invoke the place format editor.
|
||||
"""
|
||||
EditPlaceFormat(self.uistate, self.dbstate, self.track,
|
||||
self.cb_place_fmt_rebuild)
|
||||
|
||||
def cb_place_fmt_rebuild(self):
|
||||
"""
|
||||
Called to rebuild the place format list.
|
||||
"""
|
||||
model = Gtk.ListStore(str)
|
||||
for fmt in _pd.get_formats():
|
||||
model.append([fmt.name])
|
||||
self.pformat.set_model(model)
|
||||
self.pformat.set_active(0)
|
||||
|
||||
def check_for_type_changed(self, obj):
|
||||
active = obj.get_active()
|
||||
if active == 0: # update
|
||||
@@ -1228,10 +1281,6 @@ class GrampsPreferences(ConfigureDialog):
|
||||
active = obj.get_active()
|
||||
config.set('behavior.check-for-addon-updates', active)
|
||||
|
||||
def place_restrict_changed(self, obj):
|
||||
active = obj.get_active()
|
||||
config.set('preferences.place-restrict', active)
|
||||
|
||||
def date_format_changed(self, obj):
|
||||
config.set('preferences.date-format', obj.get_active())
|
||||
OkDialog(_('Change is not immediate'),
|
||||
@@ -1443,6 +1492,17 @@ class GrampsPreferences(ConfigureDialog):
|
||||
the_iter = obj.get_active_iter()
|
||||
db_choice = the_list.get_value(the_iter, 2)
|
||||
config.set('database.backend', db_choice)
|
||||
self.set_connection_widgets(db_choice)
|
||||
|
||||
def set_connection_widgets(self, db_choice):
|
||||
"""
|
||||
Sets the connection widgets sensitive for PostgreSQL.
|
||||
"""
|
||||
for widget in self.connection_widgets:
|
||||
if db_choice == 'postgresql':
|
||||
widget.set_sensitive(True)
|
||||
else:
|
||||
widget.set_sensitive(False)
|
||||
|
||||
def add_famtree_panel(self, configdialog):
|
||||
grid = Gtk.Grid()
|
||||
@@ -1452,12 +1512,22 @@ class GrampsPreferences(ConfigureDialog):
|
||||
|
||||
current_line = 0
|
||||
|
||||
if __debug__:
|
||||
lwidget = BasicLabel(_("%s: ") % _('Database backend'))
|
||||
grid.attach(lwidget, 1, current_line, 1, 1)
|
||||
obox = self.__create_backend_combo()
|
||||
grid.attach(obox, 2, current_line, 1, 1)
|
||||
current_line += 1
|
||||
lwidget = BasicLabel(_("%s: ") % _('Database backend'))
|
||||
grid.attach(lwidget, 1, current_line, 1, 1)
|
||||
obox = self.__create_backend_combo()
|
||||
grid.attach(obox, 2, current_line, 1, 1)
|
||||
current_line += 1
|
||||
|
||||
self.connection_widgets = []
|
||||
entry = self.add_entry(grid, _('Host'), current_line,
|
||||
'database.host', col_attach=1)
|
||||
self.connection_widgets.append(entry)
|
||||
current_line += 1
|
||||
entry = self.add_entry(grid, _('Port'), current_line,
|
||||
'database.port', col_attach=1)
|
||||
self.connection_widgets.append(entry)
|
||||
current_line += 1
|
||||
self.set_connection_widgets(config.get('database.backend'))
|
||||
|
||||
self.dbpath_entry = Gtk.Entry()
|
||||
self.add_path_box(grid,
|
||||
|
||||
+59
-4
@@ -65,7 +65,8 @@ from gramps.gen.db.exceptions import (DbUpgradeRequiredError,
|
||||
BsddbUpgradeRequiredError,
|
||||
BsddbDowngradeRequiredError,
|
||||
PythonUpgradeRequiredError,
|
||||
PythonDowngradeError)
|
||||
PythonDowngradeError,
|
||||
DbConnectionError)
|
||||
from .pluginmanager import GuiPluginManager
|
||||
from .dialog import (DBErrorDialog, ErrorDialog, QuestionDialog2,
|
||||
WarningDialog)
|
||||
@@ -143,7 +144,7 @@ class DbLoader(CLIDbLoader):
|
||||
return ""
|
||||
return self.import_info.info_text()
|
||||
|
||||
def read_file(self, filename):
|
||||
def read_file(self, filename, username=None, password=None):
|
||||
"""
|
||||
This method takes care of changing database, and loading the data.
|
||||
In 3.0 we only allow reading of real databases of filetype
|
||||
@@ -180,6 +181,13 @@ class DbLoader(CLIDbLoader):
|
||||
db.disable_signals()
|
||||
self.dbstate.no_database()
|
||||
|
||||
if db.requires_login() and username is None:
|
||||
login = GrampsLoginDialog(self.uistate)
|
||||
credentials = login.run()
|
||||
if credentials is None:
|
||||
return
|
||||
username, password = credentials
|
||||
|
||||
self._begin_progress()
|
||||
|
||||
force_schema_upgrade = False
|
||||
@@ -193,8 +201,9 @@ class DbLoader(CLIDbLoader):
|
||||
mode, force_schema_upgrade,
|
||||
force_bsddb_upgrade,
|
||||
force_bsddb_downgrade,
|
||||
force_python_upgrade)
|
||||
db.set_save_path(filename)
|
||||
force_python_upgrade,
|
||||
username=username,
|
||||
password=password)
|
||||
if self.dbstate.is_open():
|
||||
self.dbstate.db.close(
|
||||
user=User(callback=self._pulse_progress,
|
||||
@@ -278,6 +287,9 @@ class DbLoader(CLIDbLoader):
|
||||
except PythonDowngradeError as msg:
|
||||
self.dbstate.no_database()
|
||||
self._warn( _("Cannot open database"), str(msg))
|
||||
except DbConnectionError as msg:
|
||||
self.dbstate.no_database()
|
||||
self._warn(_("Cannot open database"), str(msg))
|
||||
except OSError as msg:
|
||||
self.dbstate.no_database()
|
||||
self._errordialog(
|
||||
@@ -380,6 +392,49 @@ def format_maker():
|
||||
box.show_all()
|
||||
return (box, type_selector)
|
||||
|
||||
class GrampsLoginDialog(ManagedWindow):
|
||||
|
||||
def __init__(self, uistate):
|
||||
"""
|
||||
A login dialog to obtain credentials to connect to a database
|
||||
"""
|
||||
self.title = _("Login")
|
||||
ManagedWindow.__init__(self, uistate, [], self.__class__, modal=True)
|
||||
|
||||
dialog = Gtk.Dialog(parent=uistate.window)
|
||||
grid = Gtk.Grid()
|
||||
grid.set_border_width(6)
|
||||
grid.set_row_spacing(6)
|
||||
grid.set_column_spacing(6)
|
||||
label = Gtk.Label(label=_('Username: '))
|
||||
grid.attach(label, 0, 0, 1, 1)
|
||||
self.username = Gtk.Entry()
|
||||
self.username.set_hexpand(True)
|
||||
grid.attach(self.username, 1, 0, 1, 1)
|
||||
label = Gtk.Label(label=_('Password: '))
|
||||
grid.attach(label, 0, 1, 1, 1)
|
||||
self.password = Gtk.Entry()
|
||||
self.password.set_hexpand(True)
|
||||
self.password.set_visibility(False)
|
||||
self.password.set_input_purpose(Gtk.InputPurpose.PASSWORD)
|
||||
grid.attach(self.password, 1, 1, 1, 1)
|
||||
dialog.vbox.pack_start(grid, True, True, 0)
|
||||
dialog.add_buttons(_('_Cancel'), Gtk.ResponseType.CANCEL,
|
||||
_('Login'), Gtk.ResponseType.OK)
|
||||
self.set_window(dialog, None, self.title)
|
||||
|
||||
def run(self):
|
||||
self.show()
|
||||
response = self.window.run()
|
||||
username = self.username.get_text()
|
||||
password = self.password.get_text()
|
||||
if response == Gtk.ResponseType.CANCEL:
|
||||
self.close()
|
||||
return None
|
||||
elif response == Gtk.ResponseType.OK:
|
||||
self.close()
|
||||
return (username, password)
|
||||
|
||||
class GrampsImportFileDialog(ManagedWindow):
|
||||
|
||||
def __init__(self, dbstate, uistate, callback=None):
|
||||
|
||||
+9
-13
@@ -285,9 +285,6 @@ class DbManager(CLIDbManager, ManagedWindow):
|
||||
# Get the current selection
|
||||
store, node = selection.get_selected()
|
||||
|
||||
if not __debug__:
|
||||
self.convert_btn.set_visible(False)
|
||||
|
||||
if not _RCS_FOUND: # it's not in Windows
|
||||
self.rcs_btn.set_visible(False)
|
||||
|
||||
@@ -560,7 +557,7 @@ class DbManager(CLIDbManager, ManagedWindow):
|
||||
if len(new_text) > 0:
|
||||
node = self.model.get_iter(path)
|
||||
old_text = self.model.get_value(node, NAME_COL)
|
||||
if self.model.get_value(node, ICON_COL) not in [None, ""]:
|
||||
if self.model.get_value(node, ICON_COL) == 'document-open':
|
||||
# this database is loaded. We must change the title
|
||||
# in case we change the name several times before quitting,
|
||||
# we save the first old name.
|
||||
@@ -661,11 +658,13 @@ class DbManager(CLIDbManager, ManagedWindow):
|
||||
Create a new database, then extracts a revision from RCS and
|
||||
imports it into the db
|
||||
"""
|
||||
new_path, newname = self._create_new_db("%s : %s" % (parent_name, name))
|
||||
dbid = config.get('database.backend')
|
||||
new_path, newname = self._create_new_db("%s : %s" % (parent_name, name),
|
||||
dbid=dbid)
|
||||
|
||||
self.__start_cursor(_("Extracting archive..."))
|
||||
|
||||
dbase = make_database("bsddb")
|
||||
dbase = make_database(dbid)
|
||||
dbase.load(new_path)
|
||||
|
||||
self.__start_cursor(_("Importing archive..."))
|
||||
@@ -811,10 +810,11 @@ class DbManager(CLIDbManager, ManagedWindow):
|
||||
while self.existing_name(new_text):
|
||||
count += 1
|
||||
new_text = "%s %s" % (name, _("(Converted #%d)") % count)
|
||||
new_path, newname = self._create_new_db(new_text, edit_entry=False)
|
||||
dbid = config.get('database.backend')
|
||||
new_path, newname = self._create_new_db(new_text, dbid=dbid,
|
||||
edit_entry=False)
|
||||
## Create a new database of correct type:
|
||||
dbase = make_database(config.get('database.backend'))
|
||||
dbase.write_version(new_path)
|
||||
dbase = make_database(dbid)
|
||||
dbase.load(new_path)
|
||||
## import from XML
|
||||
import_function = None
|
||||
@@ -929,11 +929,7 @@ class DbManager(CLIDbManager, ManagedWindow):
|
||||
fname = os.path.join(dirname, filename)
|
||||
os.unlink(fname)
|
||||
|
||||
newdb = make_database("bsddb")
|
||||
newdb.write_version(dirname)
|
||||
|
||||
dbase = make_database("bsddb")
|
||||
dbase.set_save_path(dirname)
|
||||
dbase.load(dirname, None)
|
||||
|
||||
self.__start_cursor(_("Rebuilding database from backup files"))
|
||||
|
||||
+13
-2
@@ -24,6 +24,7 @@
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
import sys
|
||||
import html
|
||||
import logging
|
||||
_LOG = logging.getLogger(".dialog")
|
||||
|
||||
@@ -34,6 +35,7 @@ _LOG = logging.getLogger(".dialog")
|
||||
#-------------------------------------------------------------------------
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Gdk
|
||||
from gi.repository import GdkPixbuf
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
@@ -45,6 +47,7 @@ from gramps.gen.const import GRAMPS_LOCALE as glocale
|
||||
_ = glocale.translation.gettext
|
||||
from gramps.gen.const import ICON, URL_BUGHOME
|
||||
from gramps.gen.config import config
|
||||
from gramps.gen.constfunc import is_quartz
|
||||
from .glade import Glade
|
||||
from .display import display_url
|
||||
|
||||
@@ -98,7 +101,8 @@ class QuestionDialog:
|
||||
self.top.set_title("%s - Gramps" % msg1)
|
||||
|
||||
label1 = self.xml.get_object('qd_label1')
|
||||
label1.set_text('<span weight="bold" size="larger">%s</span>' % msg1)
|
||||
label1.set_text('<span weight="bold" size="larger">%s</span>' %
|
||||
html.escape(msg1))
|
||||
label1.set_use_markup(True)
|
||||
|
||||
label2 = self.xml.get_object('qd_label2')
|
||||
@@ -134,7 +138,8 @@ class QuestionDialog2:
|
||||
self.top.set_title("%s - Gramps" % msg1)
|
||||
|
||||
label1 = self.xml.get_object('qd_label1')
|
||||
label1.set_text('<span weight="bold" size="larger">%s</span>' % msg1)
|
||||
label1.set_text('<span weight="bold" size="larger">%s</span>' %
|
||||
html.escape(msg1))
|
||||
label1.set_use_markup(True)
|
||||
|
||||
label2 = self.xml.get_object('qd_label2')
|
||||
@@ -503,6 +508,12 @@ def main(args):
|
||||
win = Gtk.Window()
|
||||
win.set_title('Dialog test window')
|
||||
win.set_position(Gtk.WindowPosition.CENTER)
|
||||
#Set the mnemonic modifier on Macs to alt-ctrl so that it
|
||||
#doesn't interfere with the extended keyboard, see
|
||||
#https://gramps-project.org/bugs/view.php?id=6943
|
||||
if is_quartz():
|
||||
win.set_mnemonic_modifier(
|
||||
Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.MOD1_MASK)
|
||||
def cb(window, event):
|
||||
Gtk.main_quit()
|
||||
win.connect('delete-event', cb)
|
||||
|
||||
@@ -372,6 +372,7 @@ class DisplayState(Callback):
|
||||
'filters-changed' : (str, ),
|
||||
'filter-name-changed' : (str, str, str),
|
||||
'nameformat-changed' : None,
|
||||
'placeformat-changed' : None,
|
||||
'grampletbar-close-changed' : None,
|
||||
'update-available' : (list, ),
|
||||
'autobackup' : None,
|
||||
|
||||
@@ -46,6 +46,8 @@ from .edittaglist import EditTagList
|
||||
from .editurl import EditUrl
|
||||
from .editlink import EditLink
|
||||
from .filtereditor import FilterEditor, EditFilter
|
||||
from gramps.gen.lib import (Person, Family, Event, Place, Repository, Source,
|
||||
Citation, Media, Note)
|
||||
|
||||
# Map from gramps.gen.lib name to Editor:
|
||||
EDITORS = {
|
||||
@@ -60,6 +62,18 @@ EDITORS = {
|
||||
'Note': EditNote,
|
||||
}
|
||||
|
||||
CLASSES = {
|
||||
'Person': Person,
|
||||
'Event': Event,
|
||||
'Family': Family,
|
||||
'Media': Media,
|
||||
'Source': Source,
|
||||
'Citation': Citation,
|
||||
'Place': Place,
|
||||
'Repository': Repository,
|
||||
'Note': Note,
|
||||
}
|
||||
|
||||
def EditObject(dbstate, uistate, track, obj_class, prop=None, value=None, callback=None):
|
||||
"""
|
||||
Generic Object Editor.
|
||||
@@ -69,15 +83,15 @@ def EditObject(dbstate, uistate, track, obj_class, prop=None, value=None, callba
|
||||
"""
|
||||
import logging
|
||||
LOG = logging.getLogger(".Edit")
|
||||
if obj_class in dbstate.db.get_table_names():
|
||||
if obj_class in EDITORS.keys():
|
||||
if value is None:
|
||||
obj = dbstate.db.get_table_metadata(obj_class)["class_func"]()
|
||||
obj = CLASSES[obj_class]
|
||||
try:
|
||||
EDITORS[obj_class](dbstate, uistate, track, obj, callback=callback)
|
||||
except Exception as msg:
|
||||
LOG.warning(str(msg))
|
||||
elif prop in ("gramps_id", "handle"):
|
||||
obj = dbstate.db.get_table_metadata(obj_class)[prop + "_func"](value)
|
||||
obj = dbstate.db.method('get_%s_from_%s', obj_class, prop)(value)
|
||||
if obj:
|
||||
try:
|
||||
EDITORS[obj_class](dbstate, uistate, track, obj, callback=callback)
|
||||
|
||||
@@ -43,6 +43,7 @@ _ = glocale.translation.gettext
|
||||
from ...widgets import SimpleButton
|
||||
from .grampstab import GrampsTab
|
||||
from gramps.gen.errors import WindowActiveError
|
||||
from ...utils import get_primary_mask
|
||||
|
||||
_KP_ENTER = Gdk.keyval_from_name("KP_Enter")
|
||||
_RETURN = Gdk.keyval_from_name("Return")
|
||||
@@ -219,7 +220,7 @@ class ButtonTab(GrampsTab):
|
||||
return
|
||||
self.add_button_clicked(obj)
|
||||
elif event.keyval in (_OPEN,) and self.share_btn and \
|
||||
(event.get_state() & Gdk.ModifierType.CONTROL_MASK):
|
||||
(event.get_state() & get_primary_mask()):
|
||||
self.share_button_clicked(obj)
|
||||
elif event.keyval in (_LEFT,) and \
|
||||
(event.get_state() & Gdk.ModifierType.MOD1_MASK):
|
||||
|
||||
@@ -42,6 +42,7 @@ from gi.repository import Pango
|
||||
# Gramps classes
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
from ...widgets.cellrenderertextedit import CellRendererTextEdit
|
||||
from gramps.gen.const import GRAMPS_LOCALE as glocale
|
||||
_ = glocale.translation.gettext
|
||||
from ...utils import is_right_click
|
||||
@@ -482,7 +483,10 @@ class EmbeddedList(ButtonTab):
|
||||
type_col = self._column_names[pair[1]][3]
|
||||
|
||||
if (type_col in [TEXT_COL, MARKUP_COL, TEXT_EDIT_COL]):
|
||||
renderer = Gtk.CellRendererText()
|
||||
if type_col == TEXT_EDIT_COL:
|
||||
renderer = CellRendererTextEdit()
|
||||
else:
|
||||
renderer = Gtk.CellRendererText()
|
||||
renderer.set_property('ellipsize', Pango.EllipsizeMode.END)
|
||||
if type_col == TEXT_COL or type_col == TEXT_EDIT_COL:
|
||||
column = Gtk.TreeViewColumn(name, renderer, text=pair[1])
|
||||
@@ -519,9 +523,12 @@ class EmbeddedList(ButtonTab):
|
||||
# insert the colum into the tree
|
||||
column.set_resizable(True)
|
||||
column.set_clickable(True)
|
||||
column.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
|
||||
#column.set_min_width(self._column_names[pair[1]][2])
|
||||
column.set_fixed_width(self._column_names[pair[1]][2])
|
||||
if self._column_names[pair[1]][2] != -1:
|
||||
column.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
|
||||
#column.set_min_width(self._column_names[pair[1]][2])
|
||||
column.set_fixed_width(self._column_names[pair[1]][2])
|
||||
else:
|
||||
column.set_expand(True)
|
||||
|
||||
column.set_sort_column_id(self._column_names[pair[1]][1])
|
||||
self.columns.append(column)
|
||||
|
||||
@@ -45,9 +45,10 @@ _ENTER = Gdk.keyval_from_name("Enter")
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
from .surnamemodel import SurnameModel
|
||||
from .embeddedlist import EmbeddedList, TEXT_COL, MARKUP_COL, ICON_COL
|
||||
from .embeddedlist import EmbeddedList, TEXT_EDIT_COL
|
||||
from ...ddtargets import DdTargets
|
||||
from gramps.gen.lib import Surname, NameOriginType
|
||||
from ...utils import get_primary_mask
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
@@ -70,9 +71,9 @@ class SurnameTab(EmbeddedList):
|
||||
#index = column in model. Value =
|
||||
# (name, sortcol in model, width, markup/text
|
||||
_column_names = [
|
||||
(_('Prefix'), -1, 150, TEXT_COL, -1, None),
|
||||
(_('Surname'), -1, 250, TEXT_COL, -1, None),
|
||||
(_('Connector'), -1, 100, TEXT_COL, -1, None),
|
||||
(_('Prefix'), 0, 150, TEXT_EDIT_COL, -1, None),
|
||||
(_('Surname'), 1, -1, TEXT_EDIT_COL, -1, None),
|
||||
(_('Connector'), 2, 100, TEXT_EDIT_COL, -1, None),
|
||||
]
|
||||
_column_combo = (_('Origin'), -1, 150, 3) # name, sort, width, modelcol
|
||||
_column_toggle = (_('Name|Primary'), -1, 80, 4)
|
||||
@@ -93,14 +94,6 @@ class SurnameTab(EmbeddedList):
|
||||
#first the standard text columns with normal method
|
||||
EmbeddedList.build_columns(self)
|
||||
|
||||
# Need to add attributes to renderers
|
||||
# and connect renderers to the 'edited' signal
|
||||
for colno in range(len(self.columns)):
|
||||
for renderer in self.columns[colno].get_cells():
|
||||
renderer.set_property('editable', not self.dbstate.db.readonly)
|
||||
renderer.connect('editing_started', self.on_edit_start, colno)
|
||||
renderer.connect('edited', self.on_edit_inline, self.column_order()[colno][1])
|
||||
|
||||
# now we add the two special columns
|
||||
# combobox for type
|
||||
colno = len(self.columns)
|
||||
@@ -132,7 +125,7 @@ class SurnameTab(EmbeddedList):
|
||||
column.set_resizable(True)
|
||||
column.set_sort_column_id(self._column_combo[1])
|
||||
column.set_min_width(self._column_combo[2])
|
||||
column.set_expand(True)
|
||||
column.set_expand(False)
|
||||
self.columns.append(column)
|
||||
self.tree.append_column(column)
|
||||
# toggle box for primary
|
||||
@@ -148,7 +141,7 @@ class SurnameTab(EmbeddedList):
|
||||
column.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
|
||||
column.set_alignment(0.5)
|
||||
column.set_sort_column_id(self._column_toggle[1])
|
||||
column.set_min_width(self._column_toggle[2])
|
||||
column.set_max_width(self._column_toggle[2])
|
||||
self.columns.append(column)
|
||||
self.tree.append_column(column)
|
||||
|
||||
@@ -160,6 +153,24 @@ class SurnameTab(EmbeddedList):
|
||||
## svalue = self.cmborigmap[second]
|
||||
## return glocale.strcoll(fvalue, svalue)
|
||||
|
||||
def setup_editable_col(self):
|
||||
"""
|
||||
inherit this and set the variables needed for editable columns
|
||||
Variable edit_col_funcs needs to be a dictionary from model col_nr to
|
||||
function to call for
|
||||
Example:
|
||||
self.edit_col_funcs ={1: {'edit_start': self.on_edit_start,
|
||||
'edited': self.on_edited
|
||||
}}
|
||||
"""
|
||||
self.edit_col_funcs = {
|
||||
0: {'edit_start': self.on_edit_start,
|
||||
'edited': self.on_edit_inline},
|
||||
1: {'edit_start': self.on_edit_start,
|
||||
'edited': self.on_edit_inline},
|
||||
2: {'edit_start': self.on_edit_start,
|
||||
'edited': self.on_edit_inline}}
|
||||
|
||||
def get_data(self):
|
||||
return self.obj.get_surname_list()
|
||||
|
||||
@@ -193,6 +204,16 @@ class SurnameTab(EmbeddedList):
|
||||
if self.on_change:
|
||||
self.on_change()
|
||||
|
||||
def post_rebuild(self, prebuildpath):
|
||||
"""
|
||||
Called when data model has changed, in particular necessary when row
|
||||
order is updated.
|
||||
@param prebuildpath: path selected before rebuild, None if none
|
||||
@type prebuildpath: tree path
|
||||
"""
|
||||
if self.on_change:
|
||||
self.on_change()
|
||||
|
||||
def column_order(self):
|
||||
# order of columns for EmbeddedList. Only the text columns here
|
||||
return ((1, 0), (1, 1), (1, 2))
|
||||
@@ -238,11 +259,13 @@ class SurnameTab(EmbeddedList):
|
||||
"""
|
||||
self.on_edit_start(cellr, celle, path, colnr)
|
||||
#set up autocomplete
|
||||
entry = celle.get_child()
|
||||
entry.set_width_chars(10)
|
||||
completion = Gtk.EntryCompletion()
|
||||
completion.set_model(self.cmborig)
|
||||
completion.set_minimum_key_length(1)
|
||||
completion.set_text_column(1)
|
||||
celle.get_child().set_completion(completion)
|
||||
entry.set_completion(completion)
|
||||
#
|
||||
celle.connect('changed', self.on_origcmb_change, path, colnr)
|
||||
|
||||
@@ -322,11 +345,11 @@ class SurnameTab(EmbeddedList):
|
||||
"""
|
||||
if not EmbeddedList.key_pressed(self, obj, event):
|
||||
if event.type == Gdk.EventType.KEY_PRESS and event.keyval in (_TAB,):
|
||||
if not (event.get_state() & (Gdk.ModifierType.SHIFT_MASK |
|
||||
Gdk.ModifierType.CONTROL_MASK)):
|
||||
if not (event.get_state() &
|
||||
get_primary_mask(Gdk.ModifierType.SHIFT_MASK)):
|
||||
return self.next_cell()
|
||||
elif (event.get_state() & (Gdk.ModifierType.SHIFT_MASK |
|
||||
Gdk.ModifierType.CONTROL_MASK)):
|
||||
elif (event.get_state() &
|
||||
get_primary_mask(Gdk.ModifierType.SHIFT_MASK)):
|
||||
return self.prev_cell()
|
||||
else:
|
||||
return
|
||||
|
||||
@@ -189,8 +189,9 @@ class EditLink(ManagedWindow):
|
||||
if prop == "handle":
|
||||
default = value
|
||||
elif (prop == "gramps_id" and
|
||||
object_class in self.dbstate.db.get_table_names()):
|
||||
person = self.dbstate.db.get_table_metadata(object_class)["gramps_id_func"](value)
|
||||
object_class in OBJECT_MAP.values()):
|
||||
person = self.dbstate.db.method('get_%s_from_gramps_id',
|
||||
object_class)(value)
|
||||
if person:
|
||||
default = person.handle
|
||||
d = Select(self.dbstate, self.uistate, self.track,
|
||||
|
||||
@@ -197,9 +197,6 @@ class EditPerson(EditPrimary):
|
||||
self.singsurnfr = SingSurn(self.top)
|
||||
self.multsurnfr = self.top.get_object("hboxmultsurnames")
|
||||
self.singlesurn_active = True
|
||||
self.surntab = SurnameTab(self.dbstate, self.uistate, self.track,
|
||||
self.obj.get_primary_name(),
|
||||
on_change=self._changed_name)
|
||||
|
||||
self.set_contexteventbox(self.top.get_object("eventboxtop"))
|
||||
|
||||
@@ -300,7 +297,7 @@ class EditPerson(EditPrimary):
|
||||
|
||||
def _update_families(self):
|
||||
phandle = self.obj.get_handle()
|
||||
if phandle:
|
||||
if self.dbstate.db.has_person_handle(phandle):
|
||||
#new person has no handle yet and cannot be in a family.
|
||||
person = self.dbstate.db.get_person_from_handle(phandle)
|
||||
self.obj.set_family_handle_list(person.get_family_handle_list())
|
||||
@@ -445,6 +442,9 @@ class EditPerson(EditPrimary):
|
||||
|
||||
self.preview_name = self.top.get_object("full_name")
|
||||
self.preview_name.override_font(Pango.FontDescription('sans bold 12'))
|
||||
self.surntab = SurnameTab(self.dbstate, self.uistate, self.track,
|
||||
self.obj.get_primary_name(),
|
||||
on_change=self._changed_name)
|
||||
|
||||
def get_start_date(self):
|
||||
"""
|
||||
@@ -936,7 +936,8 @@ class EditPerson(EditPrimary):
|
||||
msurhbox = self.top.get_object("hboxmultsurnames")
|
||||
msurhbox.remove(self.surntab)
|
||||
self.surntab = SurnameTab(self.dbstate, self.uistate, self.track,
|
||||
self.obj.get_primary_name())
|
||||
self.obj.get_primary_name(),
|
||||
on_change=self._changed_name)
|
||||
self.multsurnfr.set_size_request(-1,
|
||||
int(config.get('interface.surname-box-height')))
|
||||
msurhbox.pack_start(self.surntab, True, True, 0)
|
||||
|
||||
Loaded 100 of 267 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user