Compare commits

...
Author SHA1 Message Date
prculley 6ca3821f81 Release Gramps 5.0.0-beta1 2018-02-18 08:18:09 -06:00
niememat caf47940c7 Update finnish translation 2018-02-18 09:11:12 +02:00
Sam Manzi 6a78d5196d Update README for Gramps50 (#550)
* Update wiki links to 5.0
* Mention undocumented prerequisites
2018-02-18 13:15:43 +11:00
prculley fc0fdec25c Update Translation template and POTFILES.in 2018-02-17 16:36:19 -06:00
vantu5z 2adcc7d5a3 update Russian translation 2018-02-17 08:30:01 -03:00
Nick Hall fcaea3e1bb Replace backslash in image path for Windows 2018-02-16 19:33:46 +00:00
Nick Hall fc99f12568 Escape special LaTeX characters 2018-02-16 19:33:46 +00:00
Serge Noiraud 1e5a6e1f87 crash in narrative web if no unused media (#549) 2018-02-16 19:20:02 +01:00
Serge Noiraud db263ff905 Apply places changes to the narrativeweb. (#547)
* Apply places changes to the narrativeweb.

Also change order between locations and locations alternate names.

Fixes #10427

* Put the alternate locations back.

* Suppress extra line in Alternate Names section.
2018-02-16 11:10:01 +01:00
Nick Hall e202f5cd19 Copy output file from temporary directory 2018-02-14 22:43:40 +00:00
Nick Hall 2fbb9878c2 Fix character encoding for new tree category reports
Fixes #10418.
2018-02-14 18:41:45 +00:00
Leonhaeuser ba4453213a update German translation 2018-02-13 17:53:33 +01:00
prculley 5f133ffaf2 Fix Place Tree view for proper display after filter cleared.
Fixes #10416
2018-02-13 14:07:35 +00:00
Nick Hall cbac98894b Implement place formats 2018-02-13 13:58:23 +00:00
Serge Noiraud 3f368b2e02 Some dates are not translated in webcal (#545)
* Some dates are not translated in webcal

Fixes #10425

* Webcal link problem in the year glance page

* Link problems depending on LANGUAGES.
2018-02-13 10:01:17 +01:00
Sam Manzi 1e73ab5be7 Increment year to 2018 for About box. 2018-02-13 13:21:28 +11:00
niememat f428069ac6 Fix and updated translation in Finnish 2018-02-11 13:04:08 +02:00
niememat adf3772b7a Fix holidays in Finnish 2018-02-11 12:58:49 +02:00
SNoiraud 46cd191e49 Translation problem in narrative web. 2018-02-10 08:37:46 +01:00
Leonhaeuser 9afe0cc181 update German translation 2018-02-09 20:24:12 +01:00
prculley d3ed320636 fix Sidebar to resize better
Fixes #10334
Issue #10161
2018-02-08 23:35:32 +00:00
John Ralls 6f4a114fdc Update pyicu to v 2.0.3 2018-02-08 14:53:05 -08:00
John Ralls 5bec86c1f4 Consolidate Python2 and Python3 meta-modules.
GObject-Introspection now works with Python3 so the split isn't needed any more.
2018-02-08 14:53:05 -08:00
niememat 5b48691c3a Fix and updated translation in Finnish 2018-02-08 15:36:00 +02:00
Nick Hall be11466b35 Add tooltip to links in styled text editor
Fixes #10179.
2018-02-07 16:34:41 +00:00
John Ralls 0445004e7d Merge pull request #541 from jralls/accels. 2018-02-06 14:48:49 -08:00
John Ralls f688d376f5 [MacOS] Replace GDK_CONTROL_MASK with GDK_META_MASK in accelerators.
Fixes #10231 and works on all versions of Gtk3 by avoiding
GdkModifierIntent.
2018-02-06 14:46:55 -08:00
John Ralls 0a38a6352f 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.

Fixes #6943.
2018-02-06 14:46:09 -08:00
prculley 2410435d8c Fix Family Lines Graph when 'unknown' places are present
Fixes #10402
2018-02-06 22:31:40 +00:00
prculley 6f1d6fa6b4 Fix Export View to CSV when Unicode characters are present
Fixes #10404
2018-02-06 22:23:27 +00:00
prculley b1db542cb9 Fix several intl date displayers for missing parameter.
Fixes #10196
2018-02-06 22:19:23 +00:00
John Ralls 3d7479969f Update gramps.accel for gramps50 2018-02-05 14:56:34 -08:00
Zdeněk Hataš 8f4dffa1df Czech translation fixes 2018-01-31 20:31:32 +01:00
prculley ffe9f31b0b Some filter optimizations to avoid repeated parts of tree scan 2018-01-28 23:36:23 +00:00
Leonhaeuser b1af1c5435 update German translation 2018-01-27 14:55:03 +01:00
niememat 94b6fcb93f New update for Finnish translation 2018-01-27 15:19:57 +02:00
vantu5z 4b25dde6e2 update Russian translation 2018-01-27 15:56:34 +09:00
prculley f83c077a7e Fix view buttons order keeps changing on different startups
Fixes #10391
2018-01-26 22:49:59 +00:00
prculley 323d775541 Fix DescendentTree report; more space beneath Title
Issue #10377
2018-01-26 22:47:29 +00:00
prculley 4448234281 Fix DescendentTree report for crashes
Fixes #10377
2018-01-26 22:47:29 +00:00
prculley d31ced9ca5 Fix Book XML handler for unusual characters in report name
Issue #10387
2018-01-26 22:44:02 +00:00
prculley 9706af03b4 Fix 'Generate Book' dialog for bad transient parent 2018-01-26 22:44:02 +00:00
prculley b3ca17a90f Fix Book XML handler to deal with unusual characters in Book name
Fixes #10387
2018-01-26 22:44:02 +00:00
Nick Hall 4b13c95467 Add support for new genealogy tree report category
Issue #10223.
2018-01-26 22:24:29 +00:00
niememat 7864af3626 Fix and updated translation in Finnish 2018-01-26 21:58:21 +02:00
vantu5z 028f9582f5 Add color schemes to config 2018-01-26 17:30:13 +00:00
prculley cd32559282 Surname Editor; fix so changes made update Person 'Preferred Name'
Fixes #10254
2018-01-24 11:40:04 -06:00
prculley 3980fca903 Surname Editor, fix fields for better fit in minimum width dialogs 2018-01-24 11:40:04 -06:00
prculley 462b0ea20b Surname Editor; fix loss of data if using mouse to change fields
Fixes #9868, #6828, #6257
2018-01-24 11:40:04 -06:00
prculley ba4b3eaef6 Add Garbage collection after tools/reports
Fixes #10287
2018-01-22 21:14:27 +00:00
Leonhaeuser bf826637ab update German translation 2018-01-22 20:11:12 +01:00
Sam Manzi 316ff4454a Fix duplicate Add menu keybinding after PR #530 2018-01-22 13:40:19 +11:00
Paul Culley 3266625669 Main Window menu 'Add' keyboard Acellerators (Gramps50) (#530)
* Fix Add menu Accelerators so they don't activate on Shift-'x'

Fixes #10379

* Change Gramps 'Add' menu so 'New' is not used
2018-01-22 13:21:46 +11:00
niememat 90378f19c5 Fixed and updated translation in Finnish 2018-01-20 14:17:17 +02:00
vantu5z 78fb08aa12 update Russian translation 2018-01-20 18:27:48 +09:00
Paul Culley d081b75993 Export Web Family Tree; error on file write is now a message, not exception. (#524)
* Fix Export Web Family Tree for errors on file write

Fixes #10364

* Pylint on Web Family Tree export
2018-01-20 16:58:13 +11:00
Paul Culley 10e0e64ff4 Fix Citation Editor to Tab out of Confidence ComboBox (#526)
Fixes #10351
2018-01-20 16:28:15 +11:00
Serge Noiraud 3615e75d16 Narrative web: multiple problems. (#517)
* Narrative web: multiple problems.

1 - Added an option for the statistics page.
2 - We have problems with notes : pages are too large
3 - Difficult to see some page on a mobile phone when you have notes.
4 - Resizing of images on a mobile
5 - Maps are too bigs on a mobile
6 - Split the event line to have a narrower page
7 - The note is spanned on two columns (Place + Description)
7 - In media pages, the progress meter doesn't work well.
8 - Add a progress meter for the medialistpage

Issues #10344

* Narrative Web: several modifications + css files

* Stylesheet problems and reverse set_header

* Removing memory cleanup.
2018-01-18 09:03:11 +01:00
niememat 3c8cba0301 New update for Finnish translation 2018-01-18 09:58:43 +02:00
Nick Hall dde9ea8a90 Use None as the foreground colour for untagged rows in list views
The call to get_style_context is not required.  It also caused
problems in Windows due to excessive calls to the foreground_color
method.

Fixes #10365.
2018-01-13 17:20:23 +00:00
Nick Hall b7c39e51c7 Remove trailing whitespace 2018-01-09 19:51:46 +00:00
Nick Hall a216441029 Clear old custom name formats when database loaded 2018-01-09 19:09:16 +00:00
Nick Hall 89592545f3 Fix shading colour in relationship view for dark themes
Fixes #7749.
2018-01-09 18:26:26 +00:00
Nick Hall 70fff6253d Fix link colour for dark themes
Issue #7749.
2018-01-09 18:24:52 +00:00
Nick Hall 84e76de2b1 Fix default foreground colour in list views for dark themes
Issue #7749.
2018-01-09 18:21:23 +00:00
prculley fe289da983 Fix memory leak in ProgressMeter 2018-01-08 19:24:27 +00:00
prculley 371a8a8f89 fix Fan Chart for exception on right-click of person with no parent
Fixes #10349
2018-01-08 19:09:14 +00:00
prculley 1777cd4b29 Fix Fan Chart Print centering and scaling on Windows
Fixes #8460
2018-01-04 23:16:01 +00:00
Zdeněk Hataš 3971fecb29 Update Czech translation 2018-01-02 17:19:43 +01:00
Luigi Toscano 5d51155441 Italian translation updates and fixes 2017-12-31 17:34:52 +01:00
Luigi Toscano a63c9ad080 Update Italian po file from latest pot template 2017-12-31 15:08:03 +01:00
niememat f44479edfb New update for Finnish translation 2017-12-29 11:01:27 +02:00
vantu5z 738f2b7dca update Russian translation 2017-12-29 11:03:23 +03:00
vantu5z f4dc24cacd udpate pot file after 00715fc530 2017-12-29 11:03:08 +03:00
prculley eeb587e85e Fix Media Preview Gramplet for closed db
Fixes #10332
2017-12-28 22:12:22 +00:00
bertcarnell c93f2dd433 Added a requirement for pango and pangocairo versions
Eliminates a warning that was present when calling a report from
the command line.

Fixes #10333.
2017-12-28 21:47:26 +00:00
SNoiraud d4f37501a0 Narrative Web works poorly on Android browser
Fixes #010320
2017-12-28 21:33:13 +00:00
prculley 1b0ad55d13 Fix Styled Text Editor for exception on non-editable text click
Fixes #10309
Also prevent editing of links on non-editable text.
2017-12-28 19:49:21 +00:00
prculley aa2a451f23 Suport FTM 2017 Gedcom tags on import
Bug #10285
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.
2017-12-28 19:29:14 +00:00
prculley 59e367acf9 Fix FTM: rename a locked db, don't open, but title bar changes
Fixes #10303
2017-12-28 19:22:40 +00:00
prculley d8057c2b0b Fix Person, Family Sidebar Filters to add custom Event types
Fixes #10112
Also fix Family Sidebar Filter RelType init with closed db
2017-12-28 18:47:55 +00:00
niememat d624d4a0bb Update finnish translation 2017-12-28 11:48:55 +02:00
Nick Hall ac86c415a7 Use UPDATE instead of DELETE and INSERT 2017-12-12 22:25:23 +00:00
prculley b26b34fba9 Fix ProxyCache to deal with memory leak 2017-12-12 21:59:11 +00:00
prculley 4ae4ac1d12 Fix duplicated method in ProxyCache
Fixes #10291
Also changed size request to better utilize actual memory
2017-12-12 21:57:00 +00:00
Nick Hall efabb77567 Remove write_version method from the database API
This is specific to the BSDDB backend.
2017-12-12 21:35:13 +00:00
Nick Hall 46eb72d002 Fixes for in-memory databases
In-memory databases always allow write access and don't need to
use lock files.
2017-12-12 18:54:15 +00:00
Nick Hall f75a8f9497 Enable Sqlite backend for all users 2017-12-11 22:18:39 +00:00
Nick Hall 00715fc530 Move PostgreSQL backend into third-party addons 2017-12-11 21:34:47 +00:00
Nick Hall e3300cd55d Implement locking for dbapi backends 2017-12-11 21:07:12 +00:00
Nick Hall 0f7e8e73b9 Use escape from html rather than cgi package 2017-12-11 18:44:18 +00:00
prculley 1509ba526d Fix QuestionDialog display for html like characters in title
Fixes #10298
2017-12-10 19:02:53 +00:00
prculley 76084e6f52 Fix Find Database Loop tool for faster operation and better display
Fixes #10299
2017-12-10 18:52:02 +00:00
prculley 871c1a6dea Fix FamilyRelationshpType _DATAMAP order to correspond with values
Fixes #10275
2017-12-08 19:08:31 +00:00
prculley 33ebd5d0b0 Fix HandleError on IsLessThanNthGenerationAncestorOf filters
Fixes #10286
2017-12-08 18:36:34 +00:00
prculley b9b06f1668 Add additional tests to Person Filters (MatchesFilters) 2017-12-08 18:36:34 +00:00
prculley 30f3918140 Fix Relationship Graph to shows families if siblings but no parents 2017-12-08 18:28:04 +00:00
prculley e98b065719 pylint 2017-12-08 18:09:47 +00:00
prculley e7bc887f60 Fix Deep Relationship filter to avoid recursion crash
Fixes #10279
This also changes filter to use shortest paths.
2017-12-08 18:09:47 +00:00
prculley 85014bd92c Fix to allow filter progress meters in flat views 2017-12-08 18:04:24 +00:00
prculley eee82ae27e Fix Gedcom import for illegal Gedcom Family Attributes
Issue #10262
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.
2017-12-08 18:02:01 +00:00
niememat adadd6c80b Update finnish translation 2017-12-03 17:38:56 +02:00
SNoiraud 408caa6c0f Narrated Web Site: warnings reported to console
Fixes : #10283
2017-11-25 10:22:02 +01:00
Serge Noiraud 88639fcd7d Mainz css file produces left aligned FamilyMap pages (#489)
* 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)

Fixes #10268

* Many problems with css files and the ancestor tree

* media file not created if we have unused media.
2017-11-22 10:27:40 +01:00
prculley f9da7ec19a Fix Session Log for exception on closed db reference click 2017-11-20 23:03:35 +00:00
Nick Hall 45e0b9d571 Fix bug in previous commit 2017-11-20 16:44:38 +00:00
prculley 2bc4139c65 Fix 'Welcome' Gramplet and StyledTextEditor for Link handling
Issue #10179
2017-11-20 15:52:27 +00:00
prculley c759ee31eb fix Quickreport start from Editor when object not yet committed
Fixes #10048
2017-11-20 15:08:51 +00:00
prculley 12577cdfac Fix Find Database Loop tool for wrong transient parent for progress
Fixes #10072
2017-11-20 15:02:16 +00:00
prculley 288b381453 Fix Test Case Generator to not create illegal 'None' handles
Fixes #10230
2017-11-20 14:57:52 +00:00
prculley 896b9d96e2 Fix Gedcom export for bad Hebrew Months
Fixes #10245
2017-11-20 14:53:09 +00:00
Nick Hall 6b6a297a0e Revert "correct 'import' in ac2e0a26ac"
This reverts commit 620b2afa09.
2017-11-19 17:38:23 +00:00
vantu5z 07ec5c757e update Russian translation 2017-11-19 10:39:02 +03:00
niememat 5e31bca7d6 Update finnish translation 2017-11-16 12:55:37 +02:00
SNoiraud a91bc09160 Can't link to a place if wrong lat/lon in the db.
Fixes #10266
2017-11-15 16:14:47 +01:00
Zdeněk Hataš 4b7a0bafa4 Update Czech translation 2017-11-15 12:29:01 +01:00
Leonhaeuser 3b9d38e14a update German translation 2017-11-13 18:30:51 +01:00
Sam Manzi df93ce3cb0 Update redirected wiki link from GRAMPS_XML > Gramps_XML 2017-11-12 10:12:46 +11:00
SNoiraud d5776d4ddb Missing arg when right clicking in geography view.
Fixes #10261
2017-11-11 17:41:21 +01:00
vantu5z 9f0376d9aa update Russian translation 2017-11-09 11:38:25 +03:00
prculley 45bda3bacb Change INSTALL to replace 'python' with 'python3 for script invokes 2017-11-05 07:30:00 -06:00
niememat ab20047e88 Update finnish translation 2017-11-04 10:21:20 +02:00
Paul Franklin 8544f873d4 new gramps.pot translation template 2017-11-03 23:19:15 -07:00
Paul Franklin 620b2afa09 correct 'import' in ac2e0a26ac 2017-11-03 23:16:45 -07:00
niememat 759d68a9e9 Update finnish translation 2017-10-29 16:33:46 +02:00
vantu5z 205b94ad05 update Russian translation 2017-10-28 10:18:31 +03:00
Nick Hall c495edc220 Use new database convenience function
Fixes bug introduced in commit 875242c8d6.
2017-10-26 18:58:22 +01:00
Leonhaeuser 949cd6aae6 Update German translation 2017-10-26 19:27:35 +02:00
prculley 0826ba8752 Check&Repair; add checks for 'backlinks' and repair if needed. 2017-10-26 14:07:42 +01:00
prculley 12d8b62925 Fix CSV importer for place event name using gramps_id
Fixes #10239
2017-10-26 13:55:48 +01:00
prculley 9b42486f2f Fix PlaceModel to avoid exception following merge
Fixes #10184
Bug #8377
2017-10-26 13:52:15 +01:00
prculley 7e4e66d0e3 Fix Geography view 'Find' when db is closed.
Fixes #10229
2017-10-26 13:48:54 +01:00
prculley 97db4cf64e Dbapi: Fix for broken 'backlinks' after a delete.
Fixes #10221
2017-10-26 13:44:38 +01:00
prculley 455d56db1b Fix interactive search for exception on click then down arrow
Fixes #10226
2017-10-26 13:40:03 +01:00
Nick Hall 4d614ae5e5 Revert "warn user if no Ghostscript on their machine"
This reverts commit ac2e0a26ac.
2017-10-24 12:37:36 +01:00
Paul Franklin ac2e0a26ac warn user if no Ghostscript on their machine
This is the thread which motivated me to write this:
http://gramps.1791082.n4.nabble.com/Re-Relationship-graph-is-limited-to-1x1-page-td4680666.html
but unfortunately the Nabble archive of gramps was dead at
the start of that thread, so it is missing.

Here is a related commit, addressing part of the problem:
https://github.com/gramps-project/gramps/pull/472/commits/90de264ccf89578888c9c653c774632cde545b49
2017-10-23 00:17:55 -07:00
Nick Hall 0f0a9c941b Revert modifier key changes in glade files
Partially reverts commit da47949b60.

The modifier "primary" is only available in Gtk 3.20 and is
removed by the glade editor.
2017-10-19 14:07:30 +01:00
vantu5z 89908b54b3 update Russian translation 2017-10-16 09:04:25 +03:00
Nick Hall 875242c8d6 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.

Issue #9541.
2017-10-15 18:38:46 +01:00
Nick Hall 8ef49ed303 Merge pull request #472 from prculley/bug9783 2017-10-15 18:07:04 +01:00
prculley 90de264ccf 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.
2017-10-15 18:01:07 +01:00
prculley 700f15be75 Pylint on graphdoc 2017-10-15 17:53:43 +01:00
prculley 32caee3aa4 Fix relationship Graph so Unicode chars on Multiple pages works.
Fixes: #9783, #9359, #8080, #6782, #6108
Bugs: #6431, #4164, #1485, #2964, #2790
2017-10-15 17:53:43 +01:00
Nick Hall bb6d0267ac Merge pull request #474 from jralls/primary_intent 2017-10-15 17:29:17 +01:00
John Ralls 6e24c2e79a Use Gdk.Event.triggers_context_menu in osmgpsmaps.py. 2017-10-15 17:27:02 +01:00
John Ralls 00095667e8 Replace is_right_click implementation with Gdk.Event.triggers_context_menu(). 2017-10-15 17:27:02 +01:00
John Ralls da47949b60 Replace use of CONTROL_MASK with PRIMARY_INTENT.
For better UX on Macs.
2017-10-15 17:27:02 +01:00
Nick Hall 90495324e0 Write dbapi schema version to metadata table instead of a file 2017-10-14 15:46:21 +01:00
Nick Hall 50091d1f86 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
2017-10-13 22:25:49 +01:00
Nick Hall 94a6495a06 Fix opening recent family trees 2017-10-13 15:27:05 +01:00
Nick Hall c048ebcfbb Add login dialog and username/password command line options 2017-10-12 21:11:37 +01:00
prculley 1f9f08c3c5 Fix exception when changing a family with Add person editor open
fixes #10187
2017-10-12 19:20:12 +01:00
prculley 05800c530a Pylint improvements 2017-10-12 16:46:08 +01:00
prculley fdccc733a2 Remove eval() and rename self.dbstate 2017-10-12 16:45:59 +01:00
prculley 3d1095d9ca Another way to protect GOV IDs in Gramps ID position 2017-10-12 16:45:52 +01:00
Leonhaeuser 0697797504 updated German date handler: added missing Latin month names and some old German month names 2017-10-05 21:29:19 +02:00
vantu5z 8e34b04d64 update Russian translation 2017-10-03 18:25:57 +09:00
Paul Franklin e21a1c15d5 remove vestage of (old) SVN source-control system 2017-10-02 11:13:40 -07:00
Zdeněk Hataš 1aec255b38 Update Czech translation 2017-10-02 13:56:28 +02:00
Nemeséri Lajos 4c268e1236 Update Hungarian translation 2017-10-01 18:55:12 -07:00
prculley cdec0299a5 update authors file 2017-10-01 10:47:52 -05:00
Nick Hall b3b46c70b2 Remove set_save_path method
The path is already set in the database load method.
2017-09-30 21:35:41 +01:00
Nick Hall aed7560a46 Add missing methods to database unit test 2017-09-30 19:07:03 +01:00
Nick Hall f7c9ef7dbf Add missing base database method
Issue #9741.
2017-09-30 19:05:35 +01:00
Nick Hall 96f430480b Remove get_schema_version method
Issue #9541.
2017-09-30 17:32:50 +01:00
vantu5z e38614291d update Russian translation 2017-09-30 17:43:10 +09:00
Leonhaeuser 4d1bc30d64 Update German translation 2017-09-30 00:57:46 +02:00
Nick Hall 159601d635 Remove has_gramps_id method from the public API
Issue #9541.
2017-09-29 23:38:53 +01:00
Nick Hall d7cba793cf Remove get_gramps_ids method from the public API
Issue #9541.
2017-09-29 23:10:45 +01:00
Nick Hall fc92bbcdba Make get_number_of, get_raw_data and has_handle methods protected
Issue #9541.
2017-09-29 22:31:24 +01:00
Nick Hall 755254cf7c Remove get_from_name_and_gramps_id method
Issue #9541.
2017-09-29 19:42:39 +01:00
Nick Hall 5cb9c3e30e Remove get_from_name_and_handle_method
Issue #9541.
2017-09-29 18:52:58 +01:00
prculley 43c768c2d4 Fix ManagedWindow/GrampsWindowManager for subsidiary window close
fixes #10194
2017-09-29 16:56:36 +01:00
prculley e5ce8bc829 Fix Gedcom import for "1 MARR Y" issue
Fixes #10188
2017-09-29 16:53:35 +01:00
prculley d5ca13254f Correct ReorderID tool for several bugs and deal with GetGov ID
Fixes #10177, #10175, #10176
2017-09-29 15:28:58 +01:00
niememat 81a116b6a2 Update finnish translation 2017-09-28 04:44:49 +03:00
Leonhaeuser cf85f6600d Update German translation 2017-09-27 19:50:30 +02:00
SNoiraud 84c0d69da2 Bad call to unlink function.
Bug #08835
2017-09-27 09:40:12 +02:00
prculley 4ae5c6477b Fix Gedcom export for erroneously removed WriterOptionBox import 2017-09-26 16:20:42 -05:00
SNoiraud 41b97edb03 Try to handle OSError when we use the --export argument in CLI mode.
Fixes #08835
2017-09-26 16:07:41 +02:00
Nick Hall c9eee3b471 Ignore empty connection settings 2017-09-25 23:00:10 +01:00
Nick Hall 95376f9eff Include new PostgreSQL error handling in CLI 2017-09-25 22:52:05 +01:00
Paul Franklin 2b42dd258e Special char in place's name breaks xdot view
Issue #10174
2017-09-24 11:57:35 -07:00
vantu5z e1676aca6d update Russian translation 2017-09-24 19:57:14 +09:00
Paul Franklin 47ebd361d8 new gramps.pot translation template 2017-09-23 15:02:59 -07:00
Paul Franklin 62aaa65958 Organize Bookmarks Dialog doesn't close with x
Fixes #10207
2017-09-23 13:58:12 -07:00
Nick Hall 457722811d Improve PostgreSQL error handling
Re-raise a DbConnectionError if the connection raises an error.
2017-09-23 21:24:54 +01:00
Nick Hall 0f9a6d57bf Add connection preferences for PostgreSQL databases 2017-09-23 19:52:23 +01:00
Nick Hall cd602170fd Build surname list with unique names
Fixes #10186.
2017-09-23 00:38:52 +01:00
Paul Franklin c632d0f6a9 tweak some DateParser comments 2017-09-22 11:54:11 -07:00
Paul Franklin 829a8bd01d DateParserEN failures under the DateTest tool
Issue #7084
2017-09-22 11:51:30 -07:00
prculley 61552f6903 Fix make_unknown to create valid Place
Fixes #10202
2017-09-21 17:23:45 +01:00
Zdeněk Hataš 9643c6d620 Update Czech translation 2017-09-20 10:41:24 +02:00
vantu5z 4be86ba2ad update Russian translation 2017-09-18 09:29:03 +03:00
Pedro Albuquerque 42f5cd5d11 update Portuguese (Europe) translation 2017-09-17 10:09:03 -07:00
niememat 0000b03226 Update finnish translation 2017-09-17 11:47:44 +03:00
niememat 84370a75e8 Update finnish translation 2017-09-17 11:26:20 +03:00
Paul Franklin 9d89aaeac9 new gramps.pot translation template 2017-09-16 19:24:05 -07:00
Paul Franklin de02717139 consolidate two strings into one, for more context for translators 2017-09-16 18:41:23 -07:00
Paul Franklin 19bfb4160a fix typos 2017-09-16 18:35:38 -07:00
Paul Culley af975e926f StyledText.join method fails if the joint_text has StyledText Tags (#457)
fixes #10192
2017-09-17 10:48:42 +10:00
Pedro Albuquerque fe45742c21 update Portuguese (Europe) translation 2017-09-15 22:59:34 -07:00
Paul Franklin 193f082f30 3.4 database conversion fails
Fixes #10189, #10064
2017-09-15 21:04:30 -07:00
Paul Culley 231b15eca1 fix invalid export (and import) of Gedcom EMAIL records (#455)
Gedcom spec 5.5 requires '@' in general text to be doubled '@@'
fixes #10167
2017-09-10 08:33:23 +10:00
Paul Franklin c6f45af3f1 put the entire 'bash' tree in the tar file made by 'setup.py sdist' 2017-09-07 10:28:12 -07:00
Paul Franklin 96cc078118 put the entire 'example' tree in the tar file made by 'setup.py sdist' 2017-09-04 11:23:44 -07:00
Paul Franklin 4c1a82f757 restore git revision number to gramps50 branch 2017-09-02 22:08:28 -07:00
267 changed files with 35391 additions and 30521 deletions

No files matched your search

-1
View File
@@ -1,4 +1,3 @@
<!-- $Id$ -->
Alex Roitman shura@gramps-project.org
+1019 -754
View File
File diff suppressed because it is too large. Load diff
-1
View File
@@ -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$
+8 -8
View File
@@ -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
+2
View File
@@ -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 *
+196
View File
@@ -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
+13 -5
View File
@@ -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
View File
@@ -1,4 +1,3 @@
$Id$
UNSTABLE Gramps 4.0.0 Beta2 release.
+3 -1
View File
@@ -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 &lt;<html:a href="mailto:shura@gramps-project.org">shura@gramps-project.org</html:a>&gt;
@@ -94,6 +93,9 @@
<author title="contributor">
Nick Hall &lt;<html:a href="mailto:nick__hall@hotmail.com">nick__hall@hotmail.com</html:a>&gt;
</author>
<author title="contributor">
Paul Culley &lt;<html:a href="mailto:paulr2787@gmail.com">paulr2787@gmail.com</html:a>&gt;
</author>
<author title="contributor">
Peter Landgren &lt;<html:a href="mailto:peter.talken@telia.com">peter.talken@telia.com</html:a>&gt;
</author>
-55
View File
@@ -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;
}
+7 -2
View File
@@ -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;
+8 -12
View File
@@ -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
+7 -2
View File
@@ -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;
+7 -2
View File
@@ -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;
+7 -2
View File
@@ -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;
+7 -2
View File
@@ -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;
-1
View File
@@ -25,7 +25,6 @@ GRAMPS Cascading Style Sheet
Style Name: Web_Citations-Outline.css
******************************************************
# $Id$
Source Citations Outline styles
------------------------------------------------------ */
-1
View File
@@ -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
-1
View File
@@ -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
-1
View File
@@ -25,7 +25,6 @@ GRAMPS Cascading Style Sheet
Style Name: Web_Navigation-Horizontal.css
******************************************************
# $Id$
Body Element
------------------------------------------------------ */
+8 -3
View File
@@ -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
View File
@@ -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
-1
View File
@@ -41,7 +41,6 @@ black #000
white #FFF
---------------------------------------------------------------------------------------------------
# $Id$
NarrativeWeb Styles
--------------------------------------------------------------------------------------------
+8 -10
View File
@@ -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 {
+3
View File
@@ -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 {
-1
View File
@@ -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 {
+7 -4
View File
@@ -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
View File
@@ -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">
-1
View File
@@ -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"
-1
View File
@@ -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$
-->
<!--
+4 -4
View File
@@ -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)" />
+16 -16
View File
@@ -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
+4 -4
View File
@@ -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
+11 -8
View File
@@ -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>
+4 -3
View File
@@ -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)
+27
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+27 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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')
+74 -3
View File
@@ -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
"""
+1 -1
View File
@@ -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
"""
+1
View File
@@ -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']
+2 -1
View File
@@ -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
"""
+1 -1
View File
@@ -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
"""
+1 -1
View File
@@ -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
"""
+1 -1
View File
@@ -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
"""
+35 -15
View File
@@ -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
View File
@@ -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(),
-25
View File
@@ -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.
+17
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+5
View File
@@ -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
View File
@@ -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.
+3 -3
View File
@@ -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):
+20 -6
View File
@@ -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)
+4 -3
View File
@@ -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__":
+82
View File
@@ -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()
+4 -4
View File
@@ -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
View File
@@ -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"]
+4 -3
View File
@@ -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):
+6 -6
View File
@@ -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]
+1
View File
@@ -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
View File
@@ -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}]
+660
View File
@@ -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}]
+13 -6
View File
@@ -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)
+2 -1
View File
@@ -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"),
+14
View File
@@ -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
+8 -11
View File
@@ -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
-6
View File
@@ -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
+9 -8
View File
@@ -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)
+3 -1
View File
@@ -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):
+19 -1
View File
@@ -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.
-1
View 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
+1
View File
@@ -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
View File
@@ -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():
+2 -4
View File
@@ -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" +
+3 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+1
View File
@@ -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,
+17 -3
View File
@@ -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)
+2 -1
View File
@@ -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):
+11 -4
View File
@@ -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)
+42 -19
View File
@@ -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
+3 -2
View File
@@ -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,
+6 -5
View File
@@ -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