Compare commits

...

168 Commits

Author SHA1 Message Date
SNoiraud d48dcc9c5f FR10850 V1: Refresh button in the gramplet bar.
Fixes #10850

This is the first method to avoid reloading gramplet automaticaly.
2018-10-31 20:30:44 +01:00
prculley 263a082afe Merge in changes from Gramps50 2018-10-29 09:32:35 -05:00
SNoiraud f2bc982c3d NarrativeWeb fixes
Space between place, description and the event note when there are
many sources.
Change the css order between print and screen.
The chosen theme can erase prior values.
Add a width for the source column in all themes.

Fixes #10810
2018-10-21 18:43:11 +01:00
Richard Clay dd9ddab849 In personsidebarfilter, search on each part of name
Instead of requiring that the entire search string matches a single one of the Person's names, the function will require that each word in the search string matches any of the Person's name fields.

Fixes #7950
2018-10-20 21:30:20 +01:00
niememat 3046b0586c Fix translation in Finnish 2018-10-17 11:31:54 +03:00
Malte Renken cfa278886b RelGraphReport: Add an option to omit "irrelevant" family nodes 2018-10-12 20:58:04 +01:00
MJBMZ 1c430a6b91 Replace rounded corners checkbox by dropdown
It now allows rounded corners to be set more explicitly for
different genders (None/Male/Female/Both).
2018-10-12 20:25:51 +01:00
jose1711 b0eb712933 Do not use hyphen for living persons in hourglass graph 2018-10-12 19:36:42 +01:00
Jgon6 51a7ad9483 Include death anniversaries as an option in the birthdays report
Added the optional ability to list the anniversry of peoples deaths in
the birthday and anniversary report

Resolves #3540
2018-10-12 19:19:59 +01:00
Jgon6 d6e9d62fad Add an option to show years in the birthday report
Added an option to the birthday report that allows for the year of birth
(or in the case of a wedding it's year) to be printed in the report.

Resolves #5948
2018-10-12 19:19:59 +01:00
Jgon6 3bac4a1036 Added symbols to the birthdays report showing the type of event
Issue #3540
2018-10-12 19:19:59 +01:00
Jgon6 1995a7068c Birthday report dead indicator now user defineable
Fixed the birthday report so the dead icon is able to be set within the
options window
2018-10-12 19:19:59 +01:00
Jgon6 5dc06d023d Indicate if a person is dead in the birthdays report
Added a text option to have a string that will show after a persons name
in the birthday and anniversary report. This works for both birthdays
and anniversaries.

Issue #3540
2018-10-12 19:19:43 +01:00
SNoiraud d98a1857dc Make relationships optional in narrative web
Fixes #10454
2018-10-12 18:46:39 +01:00
Paul Franklin 7da8811327 Setting the year as an ordinal number in Croatian
With this commit, the "master" version of the Statistics Chart
report will show a year as an ordinal number in Croatian.

So our translators will have until 5.1.0 is released to translate
the two new strings, in all the supported gramps languages.

Issue #10822
2018-10-07 23:45:40 -07:00
Paul Franklin 375773d657 Setting the year as an ordinal number in Croatian
With this commit, two more reports now show a year as an
ordinal number in Croatian, and a year I missed before in
webcal.py is now fixed.

This commit also reverts the Croatian ordinal year fix in
the gramps50 statisticschart.py since upon reflection I
feel it would be better done in master, since then our
translators will have until 5.1.0 is released to do the
two new strings the fix requires.

Issue #10822
2018-10-07 23:43:36 -07:00
Paul Franklin 01060f0b0d name-note is not being cleared in Complete Individual report
I ran gramps with a fresh copy of the example.gramps tree,
then ran the Complete Individual report.  The options which
matter are "Entire Database" and "Include Notes" but I also
included the gramps-ID since that speeds up my work.

Then I noticed that hundreds of names at the end of the
output file all had the same note (N0001), whose text said
it referred to a specific person.

So I looked higher and the note inclusion started with I0044,
the default person.  It was real for him but not for the next
person after him, or all the people after her.

I introduced the problem when I fixed 10033, in d6a97cf90e.

Note that this bug is independent of the CIR fix in p.r. 676
and still happens even after that pull request is applied.
2018-10-07 23:36:32 -07:00
prculley 36779c1229 Fix View Column sizing so last column size setting is maintained
Fixes #10800
2018-10-07 18:14:01 +01:00
prculley 5b7f5164db Fix view so column widths are preserved when using filters
Fixes #10725
2018-10-07 18:13:51 +01:00
prculley 15db2dd603 Disable OSX specific menu items, added in 'Gramps' menu 2018-10-07 16:49:12 +01:00
prculley 97b15322d4 Add config option to use Toolbar Text 2018-10-07 16:49:12 +01:00
prculley 824bf7e40f Geps044 - Replace deprecated Gtk.UIManager, Gtk.Actions etc. 2018-10-07 16:49:12 +01:00
milotype 56fa261ef2 Better typography in graph reports
Replace hyphen with en-dash.
2018-10-07 16:16:19 +01:00
vantu5z 1e9a3841cd update Russian translation 2018-10-05 14:10:57 +03:00
Paul Franklin e6ddedf6c6 fix typo
in cbac98894b
2018-10-04 17:49:35 -07:00
Paul Franklin 325c3f22cc fix the place-format option in DDR and DAR
The place-format option was added to the Detailed Descendant
and Detailed Ancestor text reports in cbac98894b

But those two reports use the Narrator class to show many of
their places (and dates), and that class was not modified to
use the user's custom place format.

So if a user had created a custom place format and ran
either of those two reports, their places would be shown
inconsistently.  This commit fixes that.

(The non-detailed ancestor text report also uses Narrator to
show its places but that report has not had the place-format
option added to it, so this commit doesn't touch it.)
2018-10-04 09:23:38 -07:00
Paul Franklin 54be8d62f3 Setting the year as an ordinal number in Croatian
With this commit, three more reports now show a year as an
ordinal number in Croatian.

Issue #10822
2018-10-04 09:21:29 -07:00
prculley a40eca36d9 Fix bsddb for person sort with empty Surname list
Fixes #10078, #10577
2018-10-03 19:32:36 +01:00
Paul Franklin 4b17ca4724 Setting the year as an ordinal number in Croatian
When I started investigating 10822, I saw some Croatian dates
didn't seem to be working in 5.0.0, to my surprise.  I believe
I fixed that in the previous commit.

With this commit, the three reports now show a year as an
ordinal number in Croatian, so I consider 10822 fixed.

If there are any additional problems displaying Croatian,
please file additional bug reports ("one bug, one report").

Fixes #10822
2018-10-01 11:01:18 -07:00
Paul Franklin 69aaec11a5 Setting the year as an ordinal number in Croatian
When I started investigating 10822 I saw some Croatian dates
didn't seem to be working in 5.0.0, to my surprise.  There
seems to have been some kind of regression from 4.2.8.

But the Croatian translation for gramps hasn't been updated
in about three years -- except by non-Croatians.  So I don't
know why some "translated" strings which control how dates
are displayed were disabled (marked as "fuzzy") in this:
6c67053e1f

Also, I saw some code in the 4.2.8 Croatian date handler
which was not in the 5.0.0 Croatian date handler -- somehow.

So I believe this commit fixes the Croatian date handler.

Issue #10822
2018-10-01 10:59:51 -07:00
Serge Noiraud 896b77a165 Webcal: link problems in some cases (#661)
* Webcal: link problems in some cases

Year 2016 is highlighted by default instead of current year.

Fixes #10801

* Webcal: Missing links when muliyear unselected
2018-09-30 10:20:09 +02:00
Serge Noiraud 1b60193f77 Geoclose: exception when a family has no father (#662) 2018-09-29 19:20:50 +02:00
Robin van der Vliet 1efab19f1e Esperanto Translation Update eo.po (#660)
Made a lot of corrections and other stylistic improvements to the Esperanto file.
2018-09-27 11:21:15 +10:00
Paul Franklin e667431dd5 Report plugin krasch
Load data.gramps, start with the default person (I0037,
Edwin Michael Smith), run Family Descendant Tree, choose
"Start with the parent(s) of the selected first", hit OK:

.../descendtree.py", line 881, in start
    family2 = self.database.get_family_from_handle(family2_h)
...

Or start with I0057 (Anna Louise Smith), run Family Descendant
Tree, choose "Start with the parent(s) ...", hit OK:

.../descendtree.py", line 773, in start
    family2 = self.database.get_family_from_handle(family2_h)
...

Fixes #10811
2018-09-26 17:11:42 -07:00
Paul Franklin cc7e4fda85 [unhandled exception parsing "future dates" in some locales]
There is already a check in the MonitoredDate class for years
which are more than one year in the future, so such a date is
already going to be shown in red.

But some locales use the "datetime" library to parse typed-in
dates and that has a maximum year of datetime.MAXYEAR, 9999
currently, so dates greater than that produce a ValueError.

Besides adding checks for that, I have also made it so that
locales which don't need that library don't use it.

Fixes #10815
2018-09-26 14:04:52 -07:00
Paul Franklin 2490e2d07d Julian/Gregorian calendar issue when entering only year as date
when running gramps in Norwegian

Fixes #10687
2018-09-24 10:13:48 -07:00
Serge Noiraud e5c5a210b4 Events difficult to read (screen and mobile) (#658) 2018-09-23 12:27:55 +02:00
Paul Franklin 04a40c7a50 fix typo
in cf42e5a4b8 and cbac98894b

Fixes #10782
2018-09-17 21:33:44 -07:00
Leonhaeuser 44c8e3c0f0 update German translation 2018-09-08 17:03:48 +02:00
niememat 6f7242a773 New update for Finnish translation 2018-09-06 22:11:47 +03:00
niememat 358c3fc967 Update finnish translation 2018-09-06 21:58:36 +03:00
Paul Culley f1ca280441 Fix and restore Statistics Gramplet to 4.2.x status (#653)
Fixes #10754

A bit of history. bug 2060 was filed a long time ago (2008), and a patch was added to close it in 2016. I believe that the bug had already been patched by then, although I cannot be sure. The patch caused the Gramplet to update anytime the active-changed signal occurred on a person view. I suspect that this caused a fair amount of overhead on very large dbs as the entire person list was rescanned.

In any event, Doug Blank recently removed the original scan code, as well as some useful functionality. And left behind a bug where a value was always zero. The users email list had some complaints about the lost functionality, and I also saw some recent complaints that the gender statistics were incorrect. Turns out that the db's genderstats data was incorrect, probably due to crashes after db updates. The Genderstats don't get saved until db closes normally. In addition, running the Genderstats rebuild tool, did not immediately appear to correct the situation, Gramps had to be restarted to show the updated results.

I added the original statistics code back into the Gramplet, suitably updated for 5.0.x. So the Gramplet no longer depends on dbs genderstats, it scans for it now on its own.

I removed the update on active-changed function, so overhead is only present on actual db updates (which have their own signals). I tested that the add, edit, and delete person changes do properly cause the statistics to update now. So the active-changed signal is not necessary.
2018-09-06 08:59:41 +10:00
Paul Culley 482cecaa7e Fix Check and Repair to deal with bad references empty handle string (#657)
Fixes #10783

Check and Repair tool doesn't fix bad references when the reference handle is an empty string.
Found when rewriting the backlinks scan on same tool. I assumed that the prior checks had already corrected any bad forward references, and did not put in a test for them in my new code. The Travis test failed and when I debugged, I found that the tool had an exception on a handle that consisted of an empty string.

Its interesting to note that when I changed the code, a lot more citation and source references were fixed in the test.
2018-09-05 12:07:03 +10:00
Paul Culley b6c57ab3c3 Speed up Check and Repair, backlinks check stage. (#656)
Fixes #10618

* Speed up Check and Repair, backlinks check stage.

* Fix Check and repair; backlinks scan to deal with bad references
which should have been fixed in earlier checks!
2018-09-05 11:40:57 +10:00
Paul Culley f3b5f75e37 Fix strings containing deprecated illegal escape sequences (#648)
Python 3.6 and above has deprecated illegal string escape sequences. Escape sequences are preceded by a '\' and valid ones are "\n\t\r" etc. Illegal ones are not in the list https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals.

Previous to Python 3.6 these illegal sequences were ignored and the '\' was left in place. Pylint has been noting these for a while now.

This PR corrects these sequences in Gramps. I used

find . -name "*.py" | xargs -t -n 1 python3 -Wd -m py_compile 2>&1 | grep Depre

to locate the failing strings.
2018-09-04 12:03:17 +10:00
Ivan 7d9f4dcc80 [Whatsnext] check if db is open (#651)
Fixes #10732
2018-09-04 11:38:42 +10:00
Paul Culley 224748eb96 Fix usage of posixpath; should be os.path for os independence (#650)
Fixes #10740
2018-09-04 11:20:34 +10:00
Paul Culley 2aec83f057 Fix generate_checksum routine to avoid MemoryError crash (#649)
with very large files and 32-bit OS

Issue #10690
2018-09-04 10:41:27 +10:00
Paul Culley fd399323a6 Fix corrupted Bookmarks that can happen after Gramps crash (#655)
Fixes #10759
2018-09-04 10:16:21 +10:00
Paul Culley bb6b3edee2 Fix Merge Family when same parent is missing from both families (#654)
Fixes #10760
2018-09-04 10:02:39 +10:00
Paul Culley 3f7ea4418c Fix <ctrl>c in view to get selected item to clipboard (#652)
Fixes #10682
2018-09-04 09:43:49 +10:00
Paul Culley efcc115aa7 Fix Quickview Gramplet so updates work when changing active (#642)
Fixes #10713
2018-09-04 09:22:27 +10:00
Paul Culley 893c715a61 Fix place reference editor for bad cut/paste on set_latlongitude (#644)
Fixes #10719
2018-09-04 09:08:50 +10:00
Paul Culley 6bab78df21 Fix Find Database Loop Tool (bad import of _collections) (#643)
Fixes #10722
2018-09-04 08:53:22 +10:00
niememat 29019eed98 Fix translation in Finnish 2018-09-03 22:09:37 +03:00
Bernard Banko d8c006e0f7 Slovenian translation fixed to allow building mo 2018-08-25 22:27:31 +02:00
Bernard Banko b8438dddeb Slovenian translation updated 2018-08-25 22:14:42 +02:00
niememat 4ba28c637e Fix translation in Finnish 2018-08-18 12:29:06 +03:00
niememat ae84040bc7 Fix translation in Finnish 2018-08-11 18:25:28 +03:00
prculley 51e294f748 Fix up bad translation files after merge 2018-08-09 10:54:12 -05:00
Leonhaeuser cb65fd93ca Reviewed German translation Thanks to bmendl 2018-08-09 10:35:49 -05:00
niememat 5b9600d835 Fix translation in Finnish 2018-08-09 10:35:15 -05:00
John Ralls cf80d17040 File copy doesn't work if the glob can match directories. 2018-08-09 10:35:14 -05:00
John Ralls d4f906f22d Install the docs/gramps directory in the bundle.
Fixes bug #10705.
2018-08-09 10:35:14 -05:00
niememat d189e334d8 Fix and updated translation in Finnish 2018-08-09 10:35:14 -05:00
Ross Gammon a48a94d872 Update the Debian changelog after the 5.0.0 release 2018-08-09 10:35:13 -05:00
John Ralls 8e5dc220dc Release Gramps-5.0.0 on Mac. 2018-08-09 10:35:13 -05:00
Nick Hall dca2f610dc Bump to 5.0.1 2018-08-09 10:34:31 -05:00
Nick Hall 3d1833e307 Release Gramps 5.0.0 2018-08-09 10:33:57 -05:00
Nick Hall aa221cc3c7 Update Changelog and NEWS files 2018-08-09 10:33:19 -05:00
Nick Hall 51b7d1c420 Update translation template for new release 2018-08-09 10:30:58 -05:00
niememat 674d286f87 Fix translation in Finnish 2018-08-09 10:30:58 -05:00
John Ralls 0161c4b917 Correct binary test logic for primary mask.
state & get_primary_mask(another) tested (state & (primary | other)),
which will be true if state matches *either* primary *or* other, but
what is wanted in a not-negated test is state matching all bits of
(primary | other). match_primary_mask does that.

On the other hand there are also cases of "not state & (primary | other)".
no_match_primary_mask handles that, returning true if state matches none
 of the bits in (primary | other).

Fixes #10646.
2018-08-09 10:30:57 -05:00
prculley de31a42fc8 Fix BaseSelector to avoid long delay before display on large trees
Fixes #10634
2018-08-09 10:30:57 -05:00
SNoiraud 9c508de5fd Export options > Gui alignment issue
Fixes #10576
2018-08-09 10:30:57 -05:00
niememat 1b00d95ce4 New fix translation in Finnish 2018-08-09 10:30:57 -05:00
Espen Berg 78870decb8 Revised Norwegian bokmål 2018-08-09 10:30:56 -05:00
Espen Berg 1a5696eb2c Revised Norwegian bokmål 2018-08-09 10:30:24 -05:00
niememat 269d38da2c Fix translation in Finnish 2018-08-09 10:27:26 -05:00
Nick Hall 99f77b0a88 Fix dialog button order on non-Mac systems
Resolves #10585.
2018-08-09 10:27:25 -05:00
De Tran 264fdda4d0 Fix and update Vietnamese translation
Resolves #10681.
2018-08-09 10:27:24 -05:00
Sveinn í Felli 8092b88ad4 Update Icelandic translation 2018-08-09 10:12:17 -05:00
jose1711 3251e2ae88 Fix and update Slovak translation 2018-08-09 10:09:53 -05:00
jose1711 f718c5f8b4 Fix typo in translation 2018-08-09 10:09:53 -05:00
niememat a96a446b47 Fix translation in Finnish 2018-08-09 10:09:53 -05:00
niememat 5658411acf Fix translation in Finnish 2018-08-09 10:09:53 -05:00
niememat a4403d719e Fix translation in Finnish 2018-08-09 10:09:52 -05:00
Luigi Toscano 8597a10c40 Update Italian translation 2018-08-09 10:09:52 -05:00
Nick Hall d0c0045dc1 Update English (British) translation 2018-08-09 10:09:52 -05:00
De Tran 868abdc0d9 Update Vietnamese translation 2018-08-09 10:08:37 -05:00
Kaj Mikkelsen ebf88bf5c5 Update Danish translation 2018-08-09 10:08:35 -05:00
Lajos Nemeséri c3814a4e42 Update Hungarian translation 2018-08-09 10:07:53 -05:00
John Ralls 2a0b3afa4b Switch included moduleset to gitlab.gnome.org.
Fixes bug #10733.
2018-08-09 07:24:39 -07:00
John Ralls 52fe365919 Switch included moduleset to gitlab.gnome.org.
Fixes bug #10733.
2018-08-09 07:21:18 -07:00
Leonhaeuser 414aac4c7e Reviewed German translation Thanks to bmendl 2018-08-08 23:33:21 +02:00
niememat 037e26a5a3 Fix translation in Finnish 2018-08-08 14:43:17 +03:00
John Ralls 158d3a993b File copy doesn't work if the glob can match directories. 2018-08-05 13:18:01 -07:00
John Ralls 2ef35174d1 Install the docs/gramps directory in the bundle.
Fixes bug #10705.
2018-08-05 10:03:26 -07:00
niememat 3249938b88 Fix and updated translation in Finnish 2018-08-05 16:45:06 +03:00
Ross Gammon 9b18083e48 Update the Debian changelog after the 5.0.0 release 2018-07-25 21:56:42 +02:00
John Ralls 04bdeb8321 Release Gramps-5.0.0 on Mac. 2018-07-25 07:53:42 -07:00
Nick Hall 67061d58e2 Bump to 5.0.1 2018-07-24 15:11:18 +01:00
prculley bc39dda9e7 Use msgcat merged po files instead of git merge versions 2018-07-13 07:35:36 -05:00
prculley 257275f169 Merge from gramps50 2018-07-12 11:10:22 -05:00
Espen Berg 9e61809b17 Received revised Norsk Nynorsk from Sigmund Lorentsen 2018-05-01 2018-05-17 21:30:15 +02:00
Espen Berg 9e2b935db1 revised Norwegian bokmål 2018-05-17 21:28:34 +02:00
Nick Hall 9a386574d8 Revert "Clone event w/o references (2nd pass, part B)"
This reverts commit 208feceb03.

Changes requested in the PR not made.  Such a change requires
discussion on the list first.
2018-05-12 23:09:27 +01:00
Nick Hall 90bdb0a840 Revert "Embedded Clone Event w. references (2nd pass, part A)"
This reverts commit ae322dbdc8.

Changes requested in the PR not made.  Such a change requires
discussion on the list first.
2018-05-12 22:59:52 +01:00
Alois Poettker ae322dbdc8 Embedded Clone Event w. references (2nd pass, part A) 2018-04-23 11:00:26 +02:00
Alois Poettker 208feceb03 Clone event w/o references (2nd pass, part B)
Feature request #9604

This functionality in the Event list doubles an event with all the tags, citations, media, attributes and notes, but without the references. Its like an event addition, but with default values.
2018-04-23 11:00:26 +02:00
Paulo Henrique Moraes 32dd3a2e14 [pt_BR]Translation updates and corrections (#582)
- Portuguese (Brazil)
2018-03-15 11:26:59 +11:00
Jgon6 0ae51c9522 Changed behavior of "Look up with Map Services"
Removed the section that looked up by city, and country from the Map
Services lookup for Google and Open Street Map.

Resolves #9486
2018-03-03 17:35:51 +00:00
Nick Hall 59bd9f040d Merge branch 'gramps50' 2018-03-03 17:29:32 +00:00
Paulo Henrique Moraes df00dd1f32 Brazilian portuguese update 2018-02-23 14:58:38 -03:00
niememat ca49b56774 Update finnish translation 2018-02-20 18:22:28 +02:00
Nick Hall 19f8c3561c Merge branch 'gramps50' 2018-02-09 00:13:43 +00:00
Serge Noiraud 79ff9bc216 Geocoding: associate a lat/lon to a place name (#515)
* Geocoding: associate a lat/lon to a place name

Fixes #09642

* Some cleanup

* update README.md

* Remove url from message dialog
2018-01-18 09:11:51 +01:00
prculley b54672d28d Leak (Uncollected Objects); allow user to cancel long screen update 2018-01-08 19:22:27 +00:00
mb06cs 6b42d71158 endoflinereport.py: sort generation during output 2018-01-06 13:32:43 +01:00
Paweł Tomkiel ac593d814d fixing polish places translations 2018-01-04 23:07:42 +00:00
Nick Hall 617e2212c8 new gramps.pot translation template 2018-01-04 22:59:31 +00:00
Nick Hall ad15ef7961 Merge branch 'gramps50' 2018-01-03 21:32:14 +00:00
niememat 406faa5603 Update finnish translation 2017-11-25 10:44:45 +02:00
Serge Noiraud 4e0d562208 Various Feature Requests in Narrative Web (#468)
Issue #06772
2017-11-23 09:40:52 +01:00
Serge Noiraud 43ef686622 Narrative Web Feature requests : (#466)
Sort "Surname" web page by given name and birth date.
Surname list doesn't use default name format

Fixes #05315, #08067
2017-11-23 09:39:12 +01:00
Serge Noiraud d59fe6b2af Display Lat/Lon optionally on places list page (#467)
Fixes #05382
2017-11-23 09:37:47 +01:00
Serge Noiraud d677a1a785 Add extra page to narrativeweb. (#465)
Fixes #02630
2017-11-23 09:36:13 +01:00
Leonhaeuser be776d6a31 updated German date handler: added some missing Latin month names and some old German month names 2017-10-05 21:34:23 +02:00
Nick Hall 1482fedb9c Merge branch 'gramps50' 2017-09-23 17:08:52 +01:00
Sam Manzi 00f8df60ce Revert "Tidy up mocking code (#438)[First commit]"
This reverts commit 8ebb4d35fa.
2017-09-23 07:50:48 +10:00
Jonathan Biegert f31caf1ea4 enable Graphviz node port selection, optionable (p.r. 456)
This enables the headport and tailport attributes for all edges in
the Graphviz file. The default (off) value makes the arrows between
persons and/or family nodes attach their ends to the respective node
at any position. When this option is selected, the position facing
the node on the other side of the arrow is always chosen.
2017-09-17 17:20:23 -07:00
Nick Hall 8ebb4d35fa Tidy up mocking code (#438)[First commit] 2017-09-17 11:03:39 +10:00
Serge Noiraud c5f2717623 Gramps 'Views' are not named in the window header (#448)
Resolves #001675
2017-09-12 10:05:46 +10:00
Serge Noiraud e5d5cfbd3a Allow Home person to be set in Relationship and Pedigree view (#446)
Resolves #0001970
2017-09-01 10:41:24 +10:00
Sebastian Schubert bd5f6e4711 example.gramps: add some non-primary participants to events
The following additional participants were added:
Death of Garner, Anderson:
* Page, Elizabeth (Witness)
* Farmer, Miranda Keziah (Witness)
* Page, David (Informant)

Birth of Thornton, James Arthur:
* Jankowski, Margaret Jane "Maggie" (Witness)
* Garner, Raymond E. (Informant)

Marriage of Garner, Lewis Anderson and Martel, Luella Jacques:
* Garner, Robert W. (Witness)
* Martel, Henry (Witness)
* Page, Robert (Clergy)
* Blanco, L. J. (Clergy)
2017-08-08 12:41:54 +01:00
Leonhaeuser e15deff4ed synced with gramps 50 2017-08-03 20:46:41 +02:00
romjerome 9d906c29a2 update french translation 2017-08-01 12:20:05 +02:00
romjerome 6577d43e4e merge forward (working release steps on gramps42) 2017-08-01 12:03:46 +02:00
Alois Poettker 1a065485e3 Extend Pro-Gen importer II
Adapted to Pro-Gen test fix, expanded functionality and fixed
minor bugs.
2017-07-27 19:05:52 +01:00
schoonc 009783a59a Add pycharm dir to .gitignore 2017-07-27 18:23:55 +01:00
Nick Hall 1b4f70c808 Fix permissions on leak gramplet 2017-07-27 18:18:53 +01:00
niememat dd4a727521 Update finnish translation 2017-07-27 09:48:08 +03:00
Paul Culley ebb7111f25 An improved Leak (Uncollected Objects) Gramplet (#345)
* Improved leak detector (debugging tool)

* Fix DummyDb so it garbage collector can reclaim it after close

* Fix leak gramplet so pop-ups use correct transient parent
2017-07-27 12:15:38 +10:00
Nick Hall 86fd14613e Merge branch 'gramps50' 2017-07-25 21:45:37 +01:00
Alois Poettker 5276461239 Tidy up code 2017-07-18 18:25:29 +01:00
Alois Poettker 40f47bc22d Delete multiple events 2017-07-18 18:20:55 +01:00
Nick Hall 8150403ccb Revert "Simplify import plugin unit tests"
This works with python 3.6 but not with 3.4.

This reverts commit b43d94ab16.
2017-07-18 16:57:55 +01:00
Nick Hall b43d94ab16 Simplify import plugin unit tests 2017-07-18 16:15:01 +01:00
Nick Hall 849290a3b7 Fix Pro-Gen unit test 2017-07-18 00:06:30 +01:00
Leonhaeuser 0b617e9fd6 update German translation
Fix #0010131
2017-07-15 00:23:00 +02:00
Lajos Nemeséri 6eb435142e Update Hungarian translation 2017-07-08 13:34:34 +02:00
Alois Poettker e14ea777d5 Extend Pro-Gen importer functionality 2017-07-02 23:16:10 +01:00
Paul Franklin 46d73a16a0 add verify.py rules: match same birth or death date as marriage date
Issue #2583
2017-07-02 13:37:17 -07:00
Fedik 4d2b918d39 Update Ukrainian translation 2017-07-02 17:23:56 +03:00
romjerome 4acfdcce52 update french translation 2017-06-30 09:20:12 +02:00
arnaullv f3b3eec211 Updated catalan translation to latest template 2017-06-26 22:06:29 +01:00
niememat a840566e02 Update fix finnish translation 2017-06-24 19:33:00 +03:00
Nick Hall f6a2199f68 Merge branch 'gramps50' 2017-06-22 17:47:33 +01:00
Nick Hall dfc9dde32d Merge branch 'gramps50' 2017-06-15 16:44:15 +01:00
Zdeněk Hataš 50f93f54ec czech translation update 2017-06-05 11:49:50 +02:00
Nick Hall 9cb1d96725 Merge branch 'gramps50' 2017-06-03 22:20:03 +01:00
Nick Hall 40013dccc3 Merge branch 'gramps50' 2017-06-02 23:53:56 +01:00
Serge Noiraud 24af1b8420 8200: Person Relatives Tab should use the type from the relationship (#396)
* 8200: Person Relatives Tab should use the type from the relationship

* 8200: Flag variables as there are now two variables.
2017-06-02 20:07:31 +02:00
Serge Noiraud 213d656df4 10049: tabs reorganization in narrative web. (#406) 2017-06-02 13:20:21 +02:00
Nick Hall c09b4e96d2 Remove trailing whitespace 2017-05-23 19:52:19 +01:00
Nick Hall a80875e00c Update Travis for gramps51 2017-05-23 19:51:24 +01:00
Nick Hall bbdedb5097 Update a few missed version numbers 2017-05-23 19:49:23 +01:00
Nick Hall dd0671b111 Bump all version numbers to 5.1 2017-05-23 18:30:29 +01:00
239 changed files with 79420 additions and 58973 deletions
+3
View File
@@ -23,5 +23,8 @@ test/data
Thumbs.db
ehthumbs.db
# IDE
.idea/
# Glade temp files
*~
+6 -6
View File
@@ -72,7 +72,7 @@ install:
# by the automatic git checkout.
# Download Sean Ross-Ross's Pure Python module containing a framework to
# manipulate and analyze python asts and bytecode. This is loaded to
# manipulate and analyze python asts and bytecode. This is loaded to
# /home/travis/build/gramps-project/gramps/meta
# FIXME: This should be loaded from the release directory at
# https://pypi.python.org/pypi/meta
@@ -95,11 +95,11 @@ before_script:
# set GRAMPS_RESOURCES for locale, data,image and documentation
- export GRAMPS_RESOURCES=.
# Install addons
- mkdir -p ~/.gramps/gramps50/plugins/
- wget https://github.com/gramps-project/addons/raw/master/gramps50/download/CliMerge.addon.tgz
- tar -C ~/.gramps/gramps50/plugins -xzf CliMerge.addon.tgz
- wget https://github.com/gramps-project/addons/raw/master/gramps50/download/ExportRaw.addon.tgz
- tar -C ~/.gramps/gramps50/plugins -xzf ExportRaw.addon.tgz
- mkdir -p ~/.gramps/gramps51/plugins/
- wget https://github.com/gramps-project/addons/raw/master/gramps51/download/CliMerge.addon.tgz
- tar -C ~/.gramps/gramps51/plugins -xzf CliMerge.addon.tgz
- wget https://github.com/gramps-project/addons/raw/master/gramps51/download/ExportRaw.addon.tgz
- tar -C ~/.gramps/gramps51/plugins -xzf ExportRaw.addon.tgz
script:
# Ignore the virtualenv entirely. Use nosetests3, python3 (3.4.0) and coverage
+1 -3
View File
@@ -1,5 +1,5 @@
include ChangeLog
include AUTHORS
include ChangeLog
include COPYING
include FAQ
include Gramps.py
@@ -10,11 +10,9 @@ 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 *
+8 -1543
View File
File diff suppressed because it is too large Load Diff
+13 -1
View File
@@ -11,7 +11,7 @@ Requirements
The following packages **MUST** be installed in order for Gramps to work:
* **Python** 3.2 or greater - The programming language used by Gramps. https://www.python.org/
* **GTK** 3.10 or greater - A cross-platform widget toolkit for creating graphical user interfaces. http://www.gtk.org/
* **GTK** 3.12 or greater - A cross-platform widget toolkit for creating graphical user interfaces. http://www.gtk.org/
* **pygobject** 3.12 or greater - Python Bindings for GLib/GObject/GIO/GTK+ https://wiki.gnome.org/Projects/PyGObject
The following three packages with GObject Introspection bindings (the gi packages)
@@ -103,6 +103,18 @@ The following packages are optional:
More font support in the reports
* **geocodeglib**
A library use to associate a geographical position (latitude, longitude)
to a place name. This is used if you already have osmgpsmap installed.
If installed, when you add or link a place from the map, you have a red line
at the end of the table for selection.
Debian, Ubuntu, ... : gir1.2-geocodeglib-1.0
Fedora, Redhat, ... : geocode-glib
openSUSE : geocode-glib
ArchLinux : geocode-glib
...
Optional packages required by Third-party Addons
------------------------------------------------
+4
View File
@@ -330,6 +330,10 @@ table.IndividualList td.ColumnSurname {
content: "";
}
table.eventlist tbody tr td.ColumnSources {
width: 5%;
}
/* Gallery
----------------------------------------------------- */
#GalleryNav {
+1 -1
View File
@@ -489,7 +489,7 @@ table.eventlist tbody tr td.ColumnNotes {
width: 20%;
}
table.eventlist tbody tr td.ColumnSources {
width: 17%;
width: 5%;
}
table.eventlist tbody tr td.ColumnPerson {
width: 35%;
+4
View File
@@ -373,6 +373,10 @@ table.IndividualList td.ColumnSurname {
content: "";
}
table.eventlist tbody tr td.ColumnSources {
width: 5%;
}
/* Gallery
----------------------------------------------------- */
#GalleryNav {
+4
View File
@@ -366,6 +366,10 @@ table.IndividualList td.ColumnSurname {
content: "";
}
table.eventlist tbody tr td.ColumnSources {
width: 5%;
}
/* Gallery
----------------------------------------------------- */
#GalleryNav {
+4
View File
@@ -366,6 +366,10 @@ table.IndividualList td.ColumnSurname {
content: "";
}
table.eventlist tbody tr td.ColumnSources {
width: 5%;
}
/* Gallery
----------------------------------------------------- */
#GalleryNav {
+4
View File
@@ -366,6 +366,10 @@ table.IndividualList td.ColumnSurname {
content: "";
}
table.eventlist tbody tr td.ColumnSources {
width: 5%;
}
/* Gallery
----------------------------------------------------- */
#GalleryNav {
-1
View File
@@ -32,7 +32,6 @@ body {
background-color: #00029D;
color: #00029D;
width: 100%;
padding: 0px 14px;
}
/* Navigation Menus
+4
View File
@@ -312,6 +312,10 @@ table#SortByCount thead th.ColumnQuantity a:after {
content: "";
}
table.eventlist tbody tr td.ColumnSources {
width: 5%;
}
/* Gallery
-----------------------------------------------------------------*/
#GalleryNav {
+4
View File
@@ -576,6 +576,10 @@ table.relationships tbody tr td.ColumnPartner a:hover {
content: "";
}
table.eventlist tbody tr td.ColumnSources {
width: 5%;
}
/* Gallery
----------------------------------------------------- */
#Gallery { }
+722 -501
View File
File diff suppressed because it is too large Load Diff
+6
View File
@@ -1,3 +1,9 @@
gramps (5.0.0-1) unstable; urgency=medium
* New Gramps release
-- Ross Gammon <rossgammon@debian.org> Wed, 25 Jul 2018 21:19:00 +0200
gramps (5.0.0~rc1-1) unstable; urgency=medium
* First release candidate for Gramps 5.0
+38 -13
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="2018-03-08" version="5.0.0"/>
<created date="2017-08-08" version="5.1.0"/>
<researcher>
<resname>Alex Roitman,,,</resname>
</researcher>
@@ -951,7 +951,7 @@
<place hlink="_L3WJQCD3US67V2CNZT"/>
<description>Birth of Garner, Anderson</description>
</event>
<event handle="_a5af0eb7dfb557da07e" change="1284030599" id="E0179">
<event handle="_a5af0eb7dfb557da07e" change="1502187535" id="E0179">
<type>Death</type>
<dateval val="1887-04-07"/>
<place hlink="_AKFKQC2N4SM243HCTN"/>
@@ -6262,7 +6262,7 @@
<dateval val="1842-06-28"/>
<description>Death of Moreno, Joseph McDowell</description>
</event>
<event handle="_a5af0ec45b25c630e03" change="1284030608" id="E1179">
<event handle="_a5af0ec45b25c630e03" change="1502187451" id="E1179">
<type>Birth</type>
<dateval val="1911-07-12"/>
<description>Birth of Thornton, James Arthur</description>
@@ -15003,7 +15003,7 @@
<type>Marriage</type>
<description>Marriage of Johnson, Henry and Sparks, Catherine</description>
</event>
<event handle="_a5af0ed5df832ee65c1" change="1328026859" id="E2815">
<event handle="_a5af0ed5df832ee65c1" change="1502187371" id="E2815">
<type>Marriage</type>
<dateval val="1875-04-01"/>
<place hlink="_RF5KQCNJRQY8OGTX2H"/>
@@ -18649,7 +18649,7 @@
<parentin hlink="_F4CKQCJZ24JRE9Z889"/>
<citationref hlink="_c140d2472c91aad494f"/>
</person>
<person handle="_14LKQCYZJEAXTS3XX" change="1185438865" id="I1376">
<person handle="_14LKQCYZJEAXTS3XX" change="1502187492" id="I1376">
<gender>F</gender>
<name type="Birth Name">
<first>Elizabeth</first>
@@ -18657,6 +18657,7 @@
</name>
<eventref hlink="_a5af0ebb003796f79a0" role="Primary"/>
<eventref hlink="_a5af0ebb0143dab99ff" role="Primary"/>
<eventref hlink="_a5af0eb7dfb557da07e" role="Witness"/>
<childof hlink="_5IUJQCRJY47YQ8PU7N"/>
<parentin hlink="_I4LKQCOAGPFH4R90A7"/>
<citationref hlink="_c140d24731407d57b3c"/>
@@ -19904,7 +19905,7 @@
<childof hlink="_IO5KQC6H0Q6Y517LR9"/>
<citationref hlink="_c140d24a1fd2319b703"/>
</person>
<person handle="_35WJQC1B7T7NPV8OLV" change="1284030051" id="I0106">
<person handle="_35WJQC1B7T7NPV8OLV" change="1502187260" id="I0106">
<gender>M</gender>
<name type="Birth Name">
<first>Robert W.</first>
@@ -19913,6 +19914,11 @@
<eventref hlink="_a5af0eb74ac73f86aa7" role="Primary"/>
<eventref hlink="_a5af0eb74ba358391ae" role="Primary"/>
<eventref hlink="_a5af0eb74c852f7c633" role="Primary"/>
<eventref hlink="_a5af0ed5df832ee65c1" role="Witness">
<attribute type="Age" value="23">
<citationref hlink="_c140dafeb317af2fd79"/>
</attribute>
</eventref>
<childof hlink="_X3WJQCSF48F6809142"/>
<parentin hlink="_8OUJQCUVZ0XML7BQLF"/>
<citationref hlink="_c140d24a27e19bb381a"/>
@@ -21991,13 +21997,14 @@
<childof hlink="_GCDKQCHI74ZPMI5GDJ"/>
<citationref hlink="_c140d24f3f8704aa41b"/>
</person>
<person handle="_6G0KQC2UXYC66XDDC2" change="1185438865" id="I0363">
<person handle="_6G0KQC2UXYC66XDDC2" change="1502187537" id="I0363">
<gender>F</gender>
<name type="Birth Name">
<first>Miranda Keziah</first>
<surname>Farmer</surname>
</name>
<eventref hlink="_a5af0ec7a76244d962d" role="Primary"/>
<eventref hlink="_a5af0eb7dfb557da07e" role="Witness"/>
<childof hlink="_8NVJQCGMJTCL7E6ZDV"/>
<citationref hlink="_c140d24f439709d3118"/>
</person>
@@ -22989,7 +22996,7 @@
<parentin hlink="_FBVJQCFBI50TW78O49"/>
<citationref hlink="_c140d2522702664ec52"/>
</person>
<person handle="_8CLKQCT97PJOGREJ7W" change="1185438865" id="I1387">
<person handle="_8CLKQCT97PJOGREJ7W" change="1502187387" id="I1387">
<gender>M</gender>
<name type="Birth Name">
<first>Robert</first>
@@ -22997,6 +23004,11 @@
</name>
<eventref hlink="_a5af0ebb3737a6ad088" role="Primary"/>
<eventref hlink="_a5af0ebb39b441e9607" role="Primary"/>
<eventref hlink="_a5af0ed5df832ee65c1" role="Clergy">
<attribute type="Age" value="23">
<citationref hlink="_c140dafeb317af2fd79"/>
</attribute>
</eventref>
<childof hlink="_5IUJQCRJY47YQ8PU7N"/>
<parentin hlink="_H6LKQCWVIFNRNGHFVH"/>
<citationref hlink="_c140d2522d804491375"/>
@@ -23803,7 +23815,7 @@
<childof hlink="_5DBKQCVAB0XMBEW8R9"/>
<citationref hlink="_c140d25425874b0b827"/>
</person>
<person handle="_9HUJQC6ONNW8SMSKGQ" change="1185438865" id="I0038">
<person handle="_9HUJQC6ONNW8SMSKGQ" change="1502187526" id="I0038">
<gender>M</gender>
<name type="Birth Name">
<first>David</first>
@@ -23812,6 +23824,7 @@
<eventref hlink="_a5af0ec7eb514c52fbf" role="Primary"/>
<eventref hlink="_a5af0ec7ec844213b55" role="Primary"/>
<eventref hlink="_a5af0ec7ed61c743fc8" role="Primary"/>
<eventref hlink="_a5af0eb7dfb557da07e" role="Informant"/>
<childof hlink="_5IUJQCRJY47YQ8PU7N"/>
<parentin hlink="_3HUJQCK4DH582YUTZG"/>
<citationref hlink="_c140d2542c764516f13"/>
@@ -24506,7 +24519,7 @@
<parentin hlink="_8NVJQCGMJTCL7E6ZDV"/>
<citationref hlink="_c140d2560004fad8df6"/>
</person>
<person handle="_ANLKQCQSQNE5LDZMRC" change="1185438865" id="I1402">
<person handle="_ANLKQCQSQNE5LDZMRC" change="1502187434" id="I1402">
<gender>F</gender>
<name type="Birth Name">
<first>Margaret Jane &quot;Maggie&quot;</first>
@@ -24514,6 +24527,7 @@
</name>
<eventref hlink="_a5af0ebb7d47a006ee2" role="Primary"/>
<eventref hlink="_a5af0ebb7e54e14970f" role="Primary"/>
<eventref hlink="_a5af0ec45b25c630e03" role="Witness"/>
<childof hlink="_VDLKQCQQ1ADTJG1D1F"/>
<parentin hlink="_SNLKQCD0VNJ627062Y"/>
<citationref hlink="_c140d25607213be35da"/>
@@ -24882,13 +24896,18 @@
<parentin hlink="_JFYJQCG2KLRQN835JD"/>
<citationref hlink="_c140d256e3403ba129d"/>
</person>
<person handle="_B3BKQCSV0G3NKSKWDX" change="1185438865" id="I0880">
<person handle="_B3BKQCSV0G3NKSKWDX" change="1502187344" id="I0880">
<gender>F</gender>
<name type="Birth Name">
<first>L. J.</first>
<surname>Blanco</surname>
</name>
<eventref hlink="_a5af0ed2cf617169903" role="Primary"/>
<eventref hlink="_a5af0ed5df832ee65c1" role="Clergy">
<attribute type="Age" value="23">
<citationref hlink="_c140dafeb317af2fd79"/>
</attribute>
</eventref>
<childof hlink="_1BVJQCNTFAGS8273LJ"/>
<citationref hlink="_c140d256ec4306a51ca"/>
</person>
@@ -31930,7 +31949,7 @@
<childof hlink="_UDMKQC5D3A2PXPUGNC"/>
<citationref hlink="_c140d266ec93334f40f"/>
</person>
<person handle="_MG5KQC6ZKSVO4A63G2" change="1328026883" id="I0624">
<person handle="_MG5KQC6ZKSVO4A63G2" change="1502187451" id="I0624">
<gender>M</gender>
<name type="Birth Name">
<first>Raymond E.</first>
@@ -31938,6 +31957,7 @@
</name>
<eventref hlink="_a5af0ece8bd1125a1a9" role="Primary"/>
<eventref hlink="_a5af0ece8d15511ddf9" role="Primary"/>
<eventref hlink="_a5af0ec45b25c630e03" role="Informant"/>
<childof hlink="_9OUJQCBOHW9UEK9CNV"/>
<citationref hlink="_c140d266f0d5d178784"/>
</person>
@@ -32336,7 +32356,7 @@
<parentin hlink="_F4CKQCJZ24JRE9Z889"/>
<citationref hlink="_c140d267ccb7aef0cd0"/>
</person>
<person handle="_N4DKQCPEMZ7OO62O7J" change="1185438865" id="I0975">
<person handle="_N4DKQCPEMZ7OO62O7J" change="1502187308" id="I0975">
<gender>M</gender>
<name type="Birth Name">
<first>Henry</first>
@@ -32345,6 +32365,11 @@
<eventref hlink="_a5af0ed483738d4ed1e" role="Primary"/>
<eventref hlink="_a5af0ed484a144d229b" role="Primary"/>
<eventref hlink="_a5af0ed48605fb6b9eb" role="Primary"/>
<eventref hlink="_a5af0ed5df832ee65c1" role="Witness">
<attribute type="Age" value="23">
<citationref hlink="_c140dafeb317af2fd79"/>
</attribute>
</eventref>
<parentin hlink="_7PUJQC4PPS4EDIVMYE"/>
<citationref hlink="_c140d267d2128356d8e"/>
</person>
+6 -2
View File
@@ -155,7 +155,7 @@ register('behavior.translator-needed', True)
register('behavior.use-tips', False)
register('behavior.welcome', 100)
register('behavior.web-search-url', 'http://google.com/#&q=%(text)s')
register('behavior.addons-url', "https://raw.githubusercontent.com/gramps-project/addons/master/gramps50")
register('behavior.addons-url', "https://raw.githubusercontent.com/gramps-project/addons/master/gramps51")
register('database.backend', 'bsddb')
register('database.compress-backup', True)
@@ -196,6 +196,7 @@ register('interface.view-categories',
register('interface.filter', False)
register('interface.fullscreen', False)
register('interface.grampletbar-close', False)
register('interface.grampletbar-refresh', False)
register('interface.ignore-gexiv2', False)
register('interface.ignore-pil', False)
register('interface.ignore-osmgpsmap', False)
@@ -217,6 +218,7 @@ register('interface.sidebar-text', True)
register('interface.size-checked', False)
register('interface.statusbar', 1)
register('interface.toolbar-on', True)
register('interface.toolbar-text', False)
register('interface.view', True)
register('interface.surname-box-height', 150)
register('interface.treemodel-cache-size', 1000)
@@ -228,6 +230,8 @@ register('paths.report-directory', USER_HOME)
register('paths.website-directory', USER_HOME)
register('paths.website-cms-uri', '')
register('paths.website-cal-uri', '')
register('paths.website-extra-page-uri', '')
register('paths.website-extra-page-name', '')
register('paths.quick-backup-directory', USER_HOME)
register('paths.quick-backup-filename',
"%(filename)s_%(year)d-%(month)02d-%(day)02d.%(extension)s")
@@ -325,7 +329,7 @@ if not os.path.exists(CONFIGMAN.filename):
# check previous version of gramps:
fullpath, filename = os.path.split(CONFIGMAN.filename)
fullpath, previous = os.path.split(fullpath)
match = re.match('gramps(\d*)', previous)
match = re.match(r'gramps(\d*)', previous)
if match:
# cycle back looking for previous versions of gramps
for i in range(1, 20): # check back 2 gramps versions
+2 -1
View File
@@ -138,7 +138,8 @@ 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
#VERSION += "-1"
#
# Glade files
+6 -6
View File
@@ -124,12 +124,12 @@ class DateParserAR(DateParser):
_span_2 = ['إلى']
_range_1 = ['بين']
_range_2 = ['و']
self._span = re.compile("(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)" %
('|'.join(_span_1), '|'.join(_span_2)),
re.IGNORECASE)
self._range = re.compile("(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)" %
('|'.join(_range_1), '|'.join(_range_2)),
re.IGNORECASE)
self._span = re.compile(
r"(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)" %
('|'.join(_span_1), '|'.join(_span_2)), re.IGNORECASE)
self._range = re.compile(
r"(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)" %
('|'.join(_range_1), '|'.join(_range_2)), re.IGNORECASE)
#-------------------------------------------------------------------------
#
+6 -6
View File
@@ -166,12 +166,12 @@ class DateParserBG(DateParser):
_span_2 = ['до']
_range_1 = ['между']
_range_2 = ['и']
self._span = re.compile("(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)" %
('|'.join(_span_1), '|'.join(_span_2)),
re.IGNORECASE)
self._range = re.compile("(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)" %
('|'.join(_range_1), '|'.join(_range_2)),
re.IGNORECASE)
self._span = re.compile(
r"(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)" %
('|'.join(_span_1), '|'.join(_span_2)), re.IGNORECASE)
self._range = re.compile(
r"(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)" %
('|'.join(_range_1), '|'.join(_range_2)), re.IGNORECASE)
#-------------------------------------------------------------------------
#
+7 -7
View File
@@ -98,14 +98,14 @@ class DateParserCA(DateParser):
DateParser.init_strings(self)
_span_1 = ['des de']
_span_2 = ['fins a']
_range_1 = ['entre', 'ent\.', 'ent']
_range_1 = ['entre', r'ent\.', 'ent']
_range_2 = ['i']
self._span = re.compile("(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)" %
('|'.join(_span_1), '|'.join(_span_2)),
re.IGNORECASE)
self._range = re.compile("(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)" %
('|'.join(_range_1), '|'.join(_range_2)),
re.IGNORECASE)
self._span = re.compile(
r"(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)" %
('|'.join(_span_1), '|'.join(_span_2)), re.IGNORECASE)
self._range = re.compile(
r"(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)" %
('|'.join(_range_1), '|'.join(_range_2)), re.IGNORECASE)
#-------------------------------------------------------------------------
#
+5 -4
View File
@@ -89,10 +89,11 @@ class DateParserDa(DateParser):
def init_strings(self):
DateParser.init_strings(self)
self._span = re.compile("(fra)?\s*(?P<start>.+)\s*(til|--|)\s*(?P<stop>.+)",
re.IGNORECASE)
self._range = re.compile("(mellem)\s+(?P<start>.+)\s+og\s+(?P<stop>.+)",
re.IGNORECASE)
self._span = re.compile(
r"(fra)?\s*(?P<start>.+)\s*(til|--|)\s*(?P<stop>.+)",
re.IGNORECASE)
self._range = re.compile(
r"(mellem)\s+(?P<start>.+)\s+og\s+(?P<stop>.+)", re.IGNORECASE)
#-------------------------------------------------------------------------
#
+10 -8
View File
@@ -248,14 +248,16 @@ class DateParserDE(DateParser):
def init_strings(self):
DateParser.init_strings(self)
self._span = re.compile("(von|vom)\s+(?P<start>.+)\s+(bis)\s+(?P<stop>.+)",
re.IGNORECASE)
self._range = re.compile("zwischen\s+(?P<start>.+)\s+und\s+(?P<stop>.+)",
re.IGNORECASE)
self._text2 = re.compile('(\d+)?.?\s+?%s\s*((\d+)(/\d+)?)?' % self._mon_str,
re.IGNORECASE)
self._jtext2 = re.compile('(\d+)?.?\s+?%s\s*((\d+)(/\d+)?)?' % self._jmon_str,
re.IGNORECASE)
self._span = re.compile(
r"(von|vom)\s+(?P<start>.+)\s+(bis)\s+(?P<stop>.+)", re.IGNORECASE)
self._range = re.compile(
r"zwischen\s+(?P<start>.+)\s+und\s+(?P<stop>.+)", re.IGNORECASE)
self._text2 = re.compile(
r'(\d+)?.?\s+?%s\s*((\d+)(/\d+)?)?' % self._mon_str,
re.IGNORECASE)
self._jtext2 = re.compile(
r'(\d+)?.?\s+?%s\s*((\d+)(/\d+)?)?' % self._jmon_str,
re.IGNORECASE)
#-------------------------------------------------------------------------
#
+7 -7
View File
@@ -116,14 +116,14 @@ class DateParserEL(DateParser):
DateParser.init_strings(self)
_span_1 = ['από']
_span_2 = ['έως']
_range_1 = ['μετ', 'μετ\.', 'μεταξύ']
_range_1 = ['μετ', r'μετ\.', 'μεταξύ']
_range_2 = ['και']
self._span = re.compile("(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)" %
('|'.join(_span_1), '|'.join(_span_2)),
re.IGNORECASE)
self._range = re.compile("(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)" %
('|'.join(_range_1), '|'.join(_range_2)),
re.IGNORECASE)
self._span = re.compile(
r"(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)" %
('|'.join(_span_1), '|'.join(_span_2)), re.IGNORECASE)
self._range = re.compile(
r"(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)" %
('|'.join(_range_1), '|'.join(_range_2)), re.IGNORECASE)
#-------------------------------------------------------------------------
#
+7 -7
View File
@@ -97,14 +97,14 @@ class DateParserES(DateParser):
DateParser.init_strings(self)
_span_1 = ['de']
_span_2 = ['a']
_range_1 = ['entre', 'ent\.', 'ent']
_range_1 = ['entre', r'ent\.', 'ent']
_range_2 = ['y']
self._span = re.compile("(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)" %
('|'.join(_span_1), '|'.join(_span_2)),
re.IGNORECASE)
self._range = re.compile("(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)" %
('|'.join(_range_1), '|'.join(_range_2)),
re.IGNORECASE)
self._span = re.compile(
r"(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)" %
('|'.join(_span_1), '|'.join(_span_2)), re.IGNORECASE)
self._range = re.compile(
r"(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)" %
('|'.join(_range_1), '|'.join(_range_2)), re.IGNORECASE)
#-------------------------------------------------------------------------
#
+5 -5
View File
@@ -98,12 +98,12 @@ class DateParserFI(DateParser):
def init_strings(self):
DateParser.init_strings(self)
self._text2 = re.compile('(\d+)?\.?\s+?%s\.?\s*((\d+)(/\d+)?)?\s*$'
% self._mon_str, re.IGNORECASE)
self._span = re.compile("(?P<start>.+)\s+(-)\s+(?P<stop>.+)",
re.IGNORECASE)
self._text2 = re.compile(r'(\d+)?\.?\s+?%s\.?\s*((\d+)(/\d+)?)?\s*$'
% self._mon_str, re.IGNORECASE)
self._span = re.compile(r"(?P<start>.+)\s+(-)\s+(?P<stop>.+)",
re.IGNORECASE)
self._range = re.compile(
"(vuosien\s*)?(?P<start>.+)\s+ja\s+(?P<stop>.+)\s+välillä",
r"(vuosien\s*)?(?P<start>.+)\s+ja\s+(?P<stop>.+)\s+välillä",
re.IGNORECASE)
#-------------------------------------------------------------------------
+42 -6
View File
@@ -79,15 +79,27 @@ class DateParserHR(DateParser):
#~ 'персидский' : Date.CAL_PERSIAN,
#~ 'п' : Date.CAL_PERSIAN,
#~ })
# match 'Day. MONTH year.' format with or without dots
self._text2 = re.compile(r'(\d+)?\.?\s*?%s\.?\s*((\d+)(/\d+)?)?\s*\.?$'
% self._mon_str, re.IGNORECASE)
# match Day.Month.Year.
self._numeric = re.compile(
r"((\d+)[/\. ])?\s*((\d+)[/\.])?\s*(\d+)\.?$")
self._jtext2 = re.compile(r'(\d+)?.?\s+?%s\s*((\d+)(/\d+)?)?'
% self._jmon_str, re.IGNORECASE)
_span_1 = ['od']
_span_2 = ['do']
_range_1 = ['između']
_range_2 = ['i']
self._span = re.compile("(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)" %
('|'.join(_span_1), '|'.join(_span_2)),
re.IGNORECASE)
self._range = re.compile("(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)" %
('|'.join(_range_1), '|'.join(_range_2)),
self._span = re.compile(r"(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)"
% ('|'.join(_span_1), '|'.join(_span_2)),
re.IGNORECASE)
self._range = re.compile(r"(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)"
% ('|'.join(_range_1), '|'.join(_range_2)),
re.IGNORECASE)
#-------------------------------------------------------------------------
@@ -105,11 +117,35 @@ class DateDisplayHR(DateDisplay):
display = DateDisplay.display_formatted
def format_short_month_year(self, month, year, inflect, short_months):
""" Allow a subclass to modify the year, e.g. add a period """
if not hasattr(short_months[1], 'f'): # not a Lexeme: no inflection
return "{short_month} {year}.".format(
short_month = short_months[month], year = year)
return self.FORMATS_short_month_year[inflect].format(
short_month = short_months[month], year = year)
def _get_localized_year(self, year):
""" Allow a subclass to modify the year, e.g. add a period """
return year + '.'
# FIXME probably there should be a Croatian-specific "formats" (and this
# ("American comma") format (and dd_dformat03 too) should be eliminated)
def dd_dformat02(self, date_val, inflect, long_months):
""" month_name day, year """
return DateDisplay.dd_dformat02(
self, date_val, inflect, long_months).replace(' .', '')
def dd_dformat04(self, date_val, inflect, long_months):
""" day month_name year """
return DateDisplay.dd_dformat04(
self, date_val, inflect, long_months).replace(' .', '')
#-------------------------------------------------------------------------
#
# Register classes
#
#-------------------------------------------------------------------------
register_datehandler(
('hr_HR', 'hr', 'HR', 'croatian', 'Croatian', 'hrvatski', ('%d.%m.%Y',)),
('hr_HR', 'hr', 'HR', 'croatian', 'Croatian', 'hrvatski', ('%d.%m.%Y.',)),
DateParserHR, DateDisplayHR)
+9 -9
View File
@@ -228,19 +228,19 @@ class DateParserHU(DateParser):
DateParser.init_strings(self)
self._numeric = re.compile(
"((\d+)[/\.])?\s*((\d+)[/\.])?\s*(\d+)[/\. ]?$")
r"((\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']
self._text2 = re.compile(r'((\d+)(/\d+)?\.)?\s+?%s\.?\s*(\d+\.)?\s*$'
% self._mon_str, re.IGNORECASE)
_span_1 = [r'-tó\\)l', '-tól', '-től']
_span_2 = ['-ig']
_range_1 = ['és']
_range_2 = ['között']
self._span = re.compile("(?P<start>.+)(%s)\s+(?P<stop>.+)(%s)" %
('|'.join(_span_1), '|'.join(_span_2)),
re.IGNORECASE)
self._range = re.compile("(?P<start>.+)\s+(%s)\s+(?P<stop>.+)\s+(%s)" %
('|'.join(_range_1), '|'.join(_range_2)),
self._span = re.compile(r"(?P<start>.+)(%s)\s+(?P<stop>.+)(%s)" %
('|'.join(_span_1), '|'.join(_span_2)),
re.IGNORECASE)
self._range = re.compile(r"(?P<start>.+)\s+(%s)\s+(?P<stop>.+)\s+(%s)"
% ('|'.join(_range_1), '|'.join(_range_2)),
re.IGNORECASE)
+10 -8
View File
@@ -90,22 +90,23 @@ class DateParserIs(DateParser):
}
def dhformat_changed(self):
self._dhformat_parse = re.compile(".*%(\S).*%(\S).*%(\S).*%(\S).*")
self._dhformat_parse = re.compile(r".*%(\S).*%(\S).*%(\S).*%(\S).*")
def init_strings(self):
DateParser.init_strings(self)
# match 'day. month year' format
self._text2 = re.compile('(\d+)?\.?\s*?%s\.?\s*((\d+)(/\d+)?)?\s*$'
self._text2 = re.compile(r'(\d+)?\.?\s*?%s\.?\s*((\d+)(/\d+)?)?\s*$'
% self._mon_str, re.IGNORECASE)
# match 'short-day day.month year' format
short_day_str = '(' + '|'.join(self._ds.short_days[1:]) + ')'
self._numeric = re.compile("%s\s*((\d+)[\.]\s*)?((\d+)\s*)?(\d+)\s*$"
self._numeric = re.compile(r"%s\s*((\d+)[\.]\s*)?((\d+)\s*)?(\d+)\s*$"
% short_day_str, re.IGNORECASE)
self._span = re.compile("(frá)?\s*(?P<start>.+)\s*(til|--|)\s*(?P<stop>.+)",
re.IGNORECASE)
self._range = re.compile("(milli)\s+(?P<start>.+)\s+og\s+(?P<stop>.+)",
re.IGNORECASE)
self._span = re.compile(
r"(frá)?\s*(?P<start>.+)\s*(til|--|)\s*(?P<stop>.+)",
re.IGNORECASE)
self._range = re.compile(
r"(milli)\s+(?P<start>.+)\s+og\s+(?P<stop>.+)", re.IGNORECASE)
#-------------------------------------------------------------------------
#
@@ -185,7 +186,8 @@ class DateDisplayIs(DateDisplay):
text, scal)
def _get_weekday(self, date_val):
if date_val[0] == 0 or date_val[1] == 0: # no day or no month or both
if (date_val[0] == 0 or date_val[1] == 0 # no day or no month or both
or date_val[2] > datetime.MAXYEAR): # bug 10815
return ''
w_day = datetime.date(date_val[2], date_val[1], date_val[0]) # y, m, d
return self.short_days[((w_day.weekday() + 1) % 7) + 1]
+5 -5
View File
@@ -96,12 +96,12 @@ class DateParserIT(DateParser):
_span_2 = ['al', 'a']
_range_1 = ['tra', 'fra']
_range_2 = ['e']
self._span = re.compile("(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)" %
('|'.join(_span_1), '|'.join(_span_2)),
re.IGNORECASE)
self._range = re.compile("(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)" %
('|'.join(_range_1), '|'.join(_range_2)),
self._span = re.compile(r"(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)" %
('|'.join(_span_1), '|'.join(_span_2)),
re.IGNORECASE)
self._range = re.compile(
r"(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)" %
('|'.join(_range_1), '|'.join(_range_2)), re.IGNORECASE)
#-------------------------------------------------------------------------
#
+5 -5
View File
@@ -166,13 +166,13 @@ class DateParserJA(DateParser):
_span_2 = ['まで', '']
_range_1 = ['から', '', '~', '']
_range_2 = ['までの間', 'の間']
self._span = re.compile("(?P<start>.+)(%s)(?P<stop>\d+)(%s)" %
('|'.join(_span_1), '|'.join(_span_2)),
re.IGNORECASE)
self._range = re.compile("(?P<start>.+)(%s)(?P<stop>.+)(%s)" %
self._span = re.compile(r"(?P<start>.+)(%s)(?P<stop>\d+)(%s)" %
('|'.join(_span_1), '|'.join(_span_2)),
re.IGNORECASE)
self._range = re.compile(r"(?P<start>.+)(%s)(?P<stop>.+)(%s)" %
('|'.join(_range_1), '|'.join(_range_2)),
re.IGNORECASE)
self._numeric = re.compile("((\d+)年\s*)?((\d+)月\s*)?(\d+)?日?\s*$")
self._numeric = re.compile(r"((\d+)年\s*)?((\d+)月\s*)?(\d+)?日?\s*$")
#-------------------------------------------------------------------------
#
+9 -8
View File
@@ -131,18 +131,19 @@ 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)
self._text2 = re.compile(
r'((\d+)(/\d+)?)?\s+?m\.\s+%s\s*(\d+)?\s*d?\.?$'
% self._mon_str, re.IGNORECASE)
_span_1 = ['nuo']
_span_2 = ['iki']
_range_1 = ['tarp']
_range_2 = ['ir']
self._span = re.compile("(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)" %
('|'.join(_span_1), '|'.join(_span_2)),
re.IGNORECASE)
self._range = re.compile("(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)" %
('|'.join(_range_1), '|'.join(_range_2)),
re.IGNORECASE)
self._span = re.compile(r"(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)" %
('|'.join(_span_1), '|'.join(_span_2)),
re.IGNORECASE)
self._range = re.compile(
r"(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)" %
('|'.join(_range_1), '|'.join(_range_2)), re.IGNORECASE)
#-------------------------------------------------------------------------
#
+12 -6
View File
@@ -58,6 +58,7 @@ class DateParserNb(DateParser):
'innen' : Date.MOD_BEFORE,
'etter' : Date.MOD_AFTER,
'omkring' : Date.MOD_ABOUT,
'omtrent' : Date.MOD_ABOUT,
'ca' : Date.MOD_ABOUT
}
@@ -89,12 +90,13 @@ class DateParserNb(DateParser):
def init_strings(self):
DateParser.init_strings(self)
# match day. month year
self._numeric = re.compile("((\d+)[\.])?\s*((\d+))?\s*(\d+)$")
self._span = re.compile("(fra)?\s*(?P<start>.+)\s*(til|--|)\s*(?P<stop>.+)",
re.IGNORECASE)
self._range = re.compile("(mellom)\s+(?P<start>.+)\s+og\s+(?P<stop>.+)",
re.IGNORECASE)
self._numeric = re.compile(
r"((\d+)[/\.\s]\s*)?((\d+)[/\.\-\s]\s*)?(\d+)\s*$")
self._span = re.compile(
r"(fra)?\s*(?P<start>.+)\s*(til|--|)\s*(?P<stop>.+)",
re.IGNORECASE)
self._range = re.compile(
r"(mellom)\s+(?P<start>.+)\s+og\s+(?P<stop>.+)", re.IGNORECASE)
#-------------------------------------------------------------------------
#
@@ -173,6 +175,10 @@ class DateDisplayNb(DateDisplay):
return "%s%s%s%s" % (qual_str, self._mod_str[mod],
text, scal)
def dd_dformat01(self, date_val):
""" numerical -- for Norwegian dates """
return DateDisplay.dd_dformat01(self, date_val).lstrip()
#-------------------------------------------------------------------------
#
# Register classes
+7 -9
View File
@@ -119,16 +119,14 @@ class DateParserNL(DateParser):
def init_strings(self):
DateParser.init_strings(self)
self._span = re.compile("(van)\s+(?P<start>.+)\s+(tot)\s+(?P<stop>.+)",
self._span = re.compile(
r"(van)\s+(?P<start>.+)\s+(tot)\s+(?P<stop>.+)", re.IGNORECASE)
self._range = re.compile(r"tussen\s+(?P<start>.+)\s+en\s+(?P<stop>.+)",
re.IGNORECASE)
self._range = re.compile("tussen\s+(?P<start>.+)\s+en\s+(?P<stop>.+)",
re.IGNORECASE)
self._text2 = re.compile('(\d+)?.?\s+?%s\s*((\d+)(/\d+)?)?'
% self._mon_str,
re.IGNORECASE)
self._jtext2 = re.compile('(\d+)?.?\s+?%s\s*((\d+)(/\d+)?)?'
% self._jmon_str,
re.IGNORECASE)
self._text2 = re.compile(r'(\d+)?.?\s+?%s\s*((\d+)(/\d+)?)?'
% self._mon_str, re.IGNORECASE)
self._jtext2 = re.compile(r'(\d+)?.?\s+?%s\s*((\d+)(/\d+)?)?'
% self._jmon_str, re.IGNORECASE)
#-------------------------------------------------------------------------
#
+9 -6
View File
@@ -156,13 +156,16 @@ class DateParserPL(DateParser):
def init_strings(self):
DateParser.init_strings(self)
self._span = re.compile("(od)\s+(?P<start>.+)\s+(do)\s+(?P<stop>.+)", re.IGNORECASE)
self._span = re.compile(
r"(od)\s+(?P<start>.+)\s+(do)\s+(?P<stop>.+)", re.IGNORECASE)
# Also handle a common mistakes
self._range = re.compile("((?:po)?mi(?:ę|e)dzy)\s+(?P<start>.+)\s+(a)\s+(?P<stop>.+)", re.IGNORECASE)
self._text2 = re.compile('(\d+)?.?\s+?%s\s*((\d+)(/\d+)?)?' % self._mon_str,
re.IGNORECASE)
self._jtext2 = re.compile('(\d+)?.?\s+?%s\s*((\d+)(/\d+)?)?' % self._jmon_str,
re.IGNORECASE)
self._range = re.compile(
r"((?:po)?mi(?:ę|e)dzy)\s+(?P<start>.+)\s+(a)\s+(?P<stop>.+)",
re.IGNORECASE)
self._text2 = re.compile(r'(\d+)?.?\s+?%s\s*((\d+)(/\d+)?)?'
% self._mon_str, re.IGNORECASE)
self._jtext2 = re.compile(r'(\d+)?.?\s+?%s\s*((\d+)(/\d+)?)?'
% self._jmon_str, re.IGNORECASE)
#-------------------------------------------------------------------------
#
+6 -6
View File
@@ -102,13 +102,13 @@ class DateParserPT(DateParser):
DateParser.init_strings(self)
_span_1 = ['de']
_span_2 = ['a']
_range_1 = ['entre','ent\.','ent']
_range_1 = ['entre', r'ent\.', 'ent']
_range_2 = ['e']
self._span = re.compile("(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)" %
('|'.join(_span_1), '|'.join(_span_2)),
re.IGNORECASE)
self._range = re.compile("(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)" %
('|'.join(_range_1), '|'.join(_range_2)),
self._span = re.compile(r"(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)"
% ('|'.join(_span_1), '|'.join(_span_2)),
re.IGNORECASE)
self._range = re.compile(r"(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)"
% ('|'.join(_range_1), '|'.join(_range_2)),
re.IGNORECASE)
#-------------------------------------------------------------------------
+6 -6
View File
@@ -94,13 +94,13 @@ class DateParserRU(DateParser):
_span_1 = ['с', 'от']
#_span_2 = ['по', 'до'] # <-- clashes with bce parsing :-(
_span_2 = ['по']
_range_1 = ['между', 'меж\.', 'меж']
_range_1 = ['между', r'меж\.', 'меж']
_range_2 = ['и']
self._span = re.compile("(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)" %
('|'.join(_span_1), '|'.join(_span_2)),
re.IGNORECASE)
self._range = re.compile("(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)" %
('|'.join(_range_1), '|'.join(_range_2)),
self._span = re.compile(r"(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)"
% ('|'.join(_span_1), '|'.join(_span_2)),
re.IGNORECASE)
self._range = re.compile(r"(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)"
% ('|'.join(_range_1), '|'.join(_range_2)),
re.IGNORECASE)
#-------------------------------------------------------------------------
+5 -5
View File
@@ -86,11 +86,11 @@ class DateParserSK(DateParser):
_span_2 = ['do']
_range_1 = ['medzi']
_range_2 = ['a']
self._span = re.compile("(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)" %
('|'.join(_span_1), '|'.join(_span_2)),
re.IGNORECASE)
self._range = re.compile("(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)" %
('|'.join(_range_1), '|'.join(_range_2)),
self._span = re.compile(r"(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)"
% ('|'.join(_span_1), '|'.join(_span_2)),
re.IGNORECASE)
self._range = re.compile(r"(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)"
% ('|'.join(_range_1), '|'.join(_range_2)),
re.IGNORECASE)
#-------------------------------------------------------------------------
+8 -8
View File
@@ -88,18 +88,18 @@ class DateParserSL(DateParser):
DateParser.init_strings(self)
# match 'Day. MONTH year.' format with or without dots
self._text2 = re.compile('(\d+)?\.?\s*?%s\.?\s*((\d+)(/\d+)?)?\s*\.?$'
% self._mon_str, re.IGNORECASE)
self._text2 = re.compile(r'(\d+)?\.?\s*?%s\.?\s*((\d+)(/\d+)?)?\s*\.?$'
% self._mon_str, re.IGNORECASE)
# match Day.Month.Year.
self._numeric = re.compile("((\d+)[/\.-])?\s*((\d+)[/\.-])?\s*(\d+)\.?$")
self._numeric = re.compile(
r"((\d+)[/\.-])?\s*((\d+)[/\.-])?\s*(\d+)\.?$")
self._span = re.compile("od\s+(?P<start>.+)\s+do\s+(?P<stop>.+)",
self._span = re.compile(r"od\s+(?P<start>.+)\s+do\s+(?P<stop>.+)",
re.IGNORECASE)
self._range = re.compile(
"med\s+(?P<start>.+)\s+in\s+(?P<stop>.+)",
re.IGNORECASE)
self._jtext2 = re.compile('(\d+)?.?\s+?%s\s*((\d+)(/\d+)?)?'\
% self._jmon_str, re.IGNORECASE)
r"med\s+(?P<start>.+)\s+in\s+(?P<stop>.+)", re.IGNORECASE)
self._jtext2 = re.compile(r'(\d+)?.?\s+?%s\s*((\d+)(/\d+)?)?'
% self._jmon_str, re.IGNORECASE)
#-------------------------------------------------------------------------
#
+9 -8
View File
@@ -208,21 +208,22 @@ class DateParserSR(DateParser):
"""
DateParser.init_strings(self)
# match 'Day. MONTH year.' format with or without dots
self._text2 = re.compile('(\d+)?\.?\s*?%s\s*((\d+)(/\d+)?)?\.?\s*$'
% self._mon_str, re.IGNORECASE)
self._text2 = re.compile(r'(\d+)?\.?\s*?%s\s*((\d+)(/\d+)?)?\.?\s*$'
% self._mon_str, re.IGNORECASE)
# match Day.Month.Year.
self._numeric = re.compile("((\d+)[/\. ])?\s*((\d+)[/\.])?\s*(\d+)\.?$")
self._numeric = re.compile(
r"((\d+)[/\. ])?\s*((\d+)[/\.])?\s*(\d+)\.?$")
_span_1 = ['od', 'од']
_span_2 = ['do', 'до']
_range_1 = ['između', 'између']
_range_2 = ['i', 'и']
self._span = re.compile("(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)" %
('|'.join(_span_1), '|'.join(_span_2)),
re.IGNORECASE)
self._range = re.compile("(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)" %
('|'.join(_range_1), '|'.join(_range_2)),
self._span = re.compile(r"(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)"
% ('|'.join(_span_1), '|'.join(_span_2)),
re.IGNORECASE)
self._range = re.compile(r"(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)"
% ('|'.join(_range_1), '|'.join(_range_2)),
re.IGNORECASE)
#-------------------------------------------------------------------------
+9 -7
View File
@@ -95,14 +95,16 @@ class DateParserSv(DateParser):
def init_strings(self):
""" Define, in Swedish, span and range regular expressions"""
DateParser.init_strings(self)
self._numeric = re.compile("((\d+)/)?\s*((\d+)/)?\s*(\d+)[/ ]?$")
self._numeric = re.compile(r"((\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)
self._span = re.compile("(från)?\s*(?P<start>.+)\s*(till|--|)\s*(?P<stop>.+)",
re.IGNORECASE)
self._range = re.compile("(mellan)\s+(?P<start>.+)\s+och\s+(?P<stop>.+)",
re.IGNORECASE)
self._text2 = re.compile(r'((\d+)(/\d+)?)?\s+?%s\s*(\d+)?\s*$'
% self._mon_str, re.IGNORECASE)
self._span = re.compile(
r"(från)?\s*(?P<start>.+)\s*(till|--|)\s*(?P<stop>.+)",
re.IGNORECASE)
self._range = re.compile(
r"(mellan)\s+(?P<start>.+)\s+och\s+(?P<stop>.+)",
re.IGNORECASE)
#-------------------------------------------------------------------------
#
+6 -6
View File
@@ -108,14 +108,14 @@ class DateParserUK(DateParser):
_span_1 = ['з', 'від']
# b.c.e. pattern also have "до" so skip "до н."
_span_2 = ['по', 'до(?!\s+н)']
_span_2 = ['по', r'до(?!\s+н)']
_range_1 = ['між']
_range_2 = ['і', 'та']
self._span = re.compile("(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)" %
('|'.join(_span_1), '|'.join(_span_2)),
re.IGNORECASE)
self._range = re.compile("(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)" %
('|'.join(_range_1), '|'.join(_range_2)),
self._span = re.compile(r"(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)" %
('|'.join(_span_1), '|'.join(_span_2)),
re.IGNORECASE)
self._range = re.compile(r"(%s)\s+(?P<start>.+)\s+(%s)\s+(?P<stop>.+)"
% ('|'.join(_range_1), '|'.join(_range_2)),
re.IGNORECASE)
#-------------------------------------------------------------------------
+5 -5
View File
@@ -124,13 +124,13 @@ class DateParserZH_CN(DateParser):
_span_2 = ['']
_range_1 = ['介于']
_range_2 = ['']
self._span = re.compile("(%s)(?P<start>.+)(%s)(?P<stop>\d+)" %
('|'.join(_span_1), '|'.join(_span_2)),
re.IGNORECASE)
self._range = re.compile("(%s)(?P<start>.+)(%s)(?P<stop>\d+)" %
self._span = re.compile(r"(%s)(?P<start>.+)(%s)(?P<stop>\d+)" %
('|'.join(_span_1), '|'.join(_span_2)),
re.IGNORECASE)
self._range = re.compile(r"(%s)(?P<start>.+)(%s)(?P<stop>\d+)" %
('|'.join(_range_1), '|'.join(_range_2)),
re.IGNORECASE)
self._numeric = re.compile("((\d+)年\s*)?((\d+)月\s*)?(\d+)?日?\s*$")
self._numeric = re.compile(r"((\d+)年\s*)?((\d+)月\s*)?(\d+)?日?\s*$")
#-------------------------------------------------------------------------
#
+5 -5
View File
@@ -124,13 +124,13 @@ class DateParserZH_TW(DateParser):
_span_2 = ['']
_range_1 = ['介於']
_range_2 = ['']
self._span = re.compile("(%s)(?P<start>.+)(%s)(?P<stop>\d+)" %
('|'.join(_span_1), '|'.join(_span_2)),
re.IGNORECASE)
self._range = re.compile("(%s)(?P<start>.+)(%s)(?P<stop>\d+)" %
self._span = re.compile(r"(%s)(?P<start>.+)(%s)(?P<stop>\d+)" %
('|'.join(_span_1), '|'.join(_span_2)),
re.IGNORECASE)
self._range = re.compile(r"(%s)(?P<start>.+)(%s)(?P<stop>\d+)" %
('|'.join(_range_1), '|'.join(_range_2)),
re.IGNORECASE)
self._numeric = re.compile("((\d+)年\s*)?((\d+)月\s*)?(\d+)?日?\s*$")
self._numeric = re.compile(r"((\d+)年\s*)?((\d+)月\s*)?(\d+)?日?\s*$")
#-------------------------------------------------------------------------
#
+26 -16
View File
@@ -4,7 +4,7 @@
#
# Copyright (C) 2004-2006 Donald N. Allingham
# Copyright (C) 2013 Vassilii Khachaturov
# Copyright (C) 2014-2017 Paul Franklin
# Copyright (C) 2014-2018 Paul Franklin
#
# 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
@@ -570,6 +570,7 @@ class DateDisplay:
def _get_short_weekday(self, date_val):
if (date_val[0] == 0 or date_val[1] == 0 # no day or no month or both
or date_val[1] == 13 # Hebrew has 13 months
or date_val[2] > datetime.MAXYEAR # bug 10815
or date_val[2] < 0): # B.C.E. date
return ''
w_day = datetime.date(date_val[2], date_val[1], date_val[0]) # y, m, d
@@ -578,11 +579,16 @@ class DateDisplay:
def _get_long_weekday(self, date_val):
if (date_val[0] == 0 or date_val[1] == 0 # no day or no month or both
or date_val[1] == 13 # Hebrew has 13 months
or date_val[2] > datetime.MAXYEAR # bug 10815
or date_val[2] < 0): # B.C.E. date
return ''
w_day = datetime.date(date_val[2], date_val[1], date_val[0]) # y, m, d
return self.long_days[((w_day.weekday() + 1) % 7) + 1]
def _get_localized_year(self, year):
""" Allow a subclass to modify the year, e.g. add a period """
return year
def dd_dformat01(self, date_val):
"""
numerical
@@ -594,19 +600,23 @@ class DateDisplay:
return self.display_iso(date_val)
else:
if date_val[0] == date_val[1] == 0:
return str(date_val[2])
return self._get_localized_year(str(date_val[2]))
else:
value = self.dhformat.replace('%m', str(date_val[1]))
# some locales have %b for the month, e.g. ar_EG, is_IS, nb_NO
# so it would be "Jan" but as it's "numeric" I'll make it "1"
value = value.replace('%b', str(date_val[1]))
# some locales have %B for the month, e.g. ta_IN
# so it would be "January" but as it's "numeric" I'll make it 1
value = value.replace('%B', str(date_val[1]))
# some locales have %a for the abbreviated day, e.g. is_IS
value = value.replace('%a', self._get_short_weekday(date_val))
# some locales have %A for the long/full day, e.g. ta_IN
value = value.replace('%A', self._get_long_weekday(date_val))
if '%b' in value or '%B' in value:
# some locales have %b for the month (ar_EG, is_IS, nb_NO)
# so it would be "Jan" but as it's "numeric" make it "1"
value = value.replace('%b', str(date_val[1]))
# some locales have %B for the month, e.g. ta_IN
# so it would be "January" but as it's "numeric" make it 1
value = value.replace('%B', str(date_val[1]))
if '%a' in value or '%A' in value:
# some locales have %a for the abbreviated day, e.g. is_IS
value = value.replace('%a',
self._get_short_weekday(date_val))
# some locales have %A for the long/full day, e.g. ta_IN
value = value.replace('%A',
self._get_long_weekday(date_val))
if date_val[0] == 0: # ignore the zero day and its delimiter
i_day = value.find('%d')
if len(value) == i_day + 2: # delimiter is left of the day
@@ -628,7 +638,7 @@ class DateDisplay:
year = self._slash_year(date_val[2], date_val[3])
if date_val[0] == 0:
if date_val[1] == 0:
return year
return self._get_localized_year(year)
else:
return self.format_long_month_year(date_val[1], year,
inflect, long_months)
@@ -654,7 +664,7 @@ class DateDisplay:
year = self._slash_year(date_val[2], date_val[3])
if date_val[0] == 0:
if date_val[1] == 0:
return year
return self._get_localized_year(year)
else:
return self.format_short_month_year(date_val[1], year,
inflect, short_months)
@@ -680,7 +690,7 @@ class DateDisplay:
year = self._slash_year(date_val[2], date_val[3])
if date_val[0] == 0:
if date_val[1] == 0:
return year
return self._get_localized_year(year)
else:
return self.format_long_month_year(date_val[1], year,
inflect, long_months)
@@ -706,7 +716,7 @@ class DateDisplay:
year = self._slash_year(date_val[2], date_val[3])
if date_val[0] == 0:
if date_val[1] == 0:
return year
return self._get_localized_year(year)
else:
return self.format_short_month_year(date_val[1], year,
inflect, short_months)
+57 -51
View File
@@ -193,7 +193,7 @@ class DateParser:
converted, the text string is assigned.
"""
_dhformat_parse = re.compile(".*%(\S).*%(\S).*%(\S).*")
_dhformat_parse = re.compile(r".*%(\S).*%(\S).*%(\S).*")
# RFC-2822 only uses capitalized English abbreviated names, no locales.
_rfc_days = ('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat')
@@ -423,63 +423,69 @@ class DateParser:
# bce, calendar type and quality may be either at the end or at
# the beginning of the given date string, therefore they will
# be parsed from the middle and will be in match.group(2).
self._bce_re = re.compile("(.*)\s+%s( ?.*)" % self._bce_str)
self._bce_re = re.compile(r"(.*)\s+%s( ?.*)" % self._bce_str)
self._cal = re.compile("(.*)\s+\(%s\)( ?.*)" % self._cal_str,
re.IGNORECASE)
self._calny = re.compile("(.*)\s+\(%s,\s*%s\)( ?.*)" % (self._cal_str,
self._ny_str),
re.IGNORECASE)
self._calny_iso = re.compile("(.*)\s+\(%s,\s*(\d{1,2}-\d{1,2})\)( ?.*)" % self._cal_str,
re.IGNORECASE)
self._cal = re.compile(r"(.*)\s+\(%s\)( ?.*)" % self._cal_str,
re.IGNORECASE)
self._calny = re.compile(r"(.*)\s+\(%s,\s*%s\)( ?.*)" %
(self._cal_str, self._ny_str), re.IGNORECASE)
self._calny_iso = re.compile(
r"(.*)\s+\(%s,\s*(\d{1,2}-\d{1,2})\)( ?.*)" % self._cal_str,
re.IGNORECASE)
self._ny = re.compile("(.*)\s+\(%s\)( ?.*)" % self._ny_str,
re.IGNORECASE)
self._ny_iso = re.compile("(.*)\s+\((\d{1,2}-\d{1,2})\)( ?.*)")
self._ny = re.compile(r"(.*)\s+\(%s\)( ?.*)" % self._ny_str,
re.IGNORECASE)
self._ny_iso = re.compile(r"(.*)\s+\((\d{1,2}-\d{1,2})\)( ?.*)")
self._qual = re.compile("(.* ?)%s\s+(.+)" % self._qual_str,
re.IGNORECASE)
self._qual = re.compile(r"(.* ?)%s\s+(.+)" % self._qual_str,
re.IGNORECASE)
self._span = re.compile("(from)\s+(?P<start>.+)\s+to\s+(?P<stop>.+)",
self._span = re.compile(r"(from)\s+(?P<start>.+)\s+to\s+(?P<stop>.+)",
re.IGNORECASE)
self._range = re.compile(
r"(bet|bet.|between)\s+(?P<start>.+)\s+and\s+(?P<stop>.+)",
re.IGNORECASE)
self._modifier = re.compile(r'%s\s+(.*)' % self._mod_str,
re.IGNORECASE)
self._range = re.compile("(bet|bet.|between)\s+(?P<start>.+)\s+and\s+(?P<stop>.+)",
re.IGNORECASE)
self._modifier = re.compile('%s\s+(.*)' % self._mod_str,
re.IGNORECASE)
self._modifier_after = re.compile('(.*)\s+%s' % self._mod_after_str,
self._modifier_after = re.compile(r'(.*)\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._abt2 = re.compile('<(.*)>', re.IGNORECASE)
self._text = re.compile(r'%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)
self._jtext = re.compile('%s\s+(\d+)?\s*,?\s*((\d+)(/\d+)?)?\s*$' % self._jmon_str,
re.IGNORECASE)
self._jtext2 = re.compile('(\d+)?\s+?%s\s*((\d+)(/\d+)?)?\s*$' % self._jmon_str,
re.IGNORECASE)
self._ftext = re.compile('%s\s+(\d+)?\s*,?\s*((\d+)(/\d+)?)?\s*$' % self._fmon_str,
re.IGNORECASE)
self._ftext2 = re.compile('(\d+)?\s+?%s\s*((\d+)(/\d+)?)?\s*$' % self._fmon_str,
re.IGNORECASE)
self._ptext = re.compile('%s\s+(\d+)?\s*,?\s*((\d+)(/\d+)?)?\s*$' % self._pmon_str,
re.IGNORECASE)
self._ptext2 = re.compile('(\d+)?\s+?%s\s*((\d+)(/\d+)?)?\s*$' % self._pmon_str,
re.IGNORECASE)
self._itext = re.compile('%s\s+(\d+)?\s*,?\s*((\d+)(/\d+)?)?\s*$' % self._imon_str,
re.IGNORECASE)
self._itext2 = re.compile('(\d+)?\s+?%s\s*((\d+)(/\d+)?)?\s*$' % self._imon_str,
re.IGNORECASE)
self._stext = re.compile('%s\.?\s+(\d+)?\s*,?\s*((\d+)(/\d+)?)?\s*$' % self._smon_str,
re.IGNORECASE)
self._stext2 = re.compile('(\d+)?\s+?%s\.?\s*((\d+)(/\d+)?)?\s*$' % self._smon_str,
re.IGNORECASE)
self._numeric = re.compile("((\d+)[/\.]\s*)?((\d+)[/\.]\s*)?(\d+)\s*$")
self._iso = re.compile("(\d+)(/(\d+))?-(\d+)-(\d+)\s*$")
self._isotimestamp = re.compile("^\s*?(\d{4})([01]\d)([0123]\d)(?:(?:[012]\d[0-5]\d[0-5]\d)|(?:\s+[012]\d:[0-5]\d(?::[0-5]\d)?))?\s*?$")
self._rfc = re.compile("(%s,)?\s+(\d|\d\d)\s+%s\s+(\d+)\s+\d\d:\d\d(:\d\d)?\s+(\+|-)\d\d\d\d"
% (self._rfc_day_str, self._rfc_mon_str))
self._today = re.compile("^\s*%s\s*$" % self._today_str, re.IGNORECASE)
self._text2 = re.compile(r'(\d+)?\s+?%s\.?\s*((\d+)(/\d+)?)?\s*$'
% self._mon_str, re.IGNORECASE)
self._jtext = re.compile(r'%s\s+(\d+)?\s*,?\s*((\d+)(/\d+)?)?\s*$'
% self._jmon_str, re.IGNORECASE)
self._jtext2 = re.compile(r'(\d+)?\s+?%s\s*((\d+)(/\d+)?)?\s*$'
% self._jmon_str, re.IGNORECASE)
self._ftext = re.compile(r'%s\s+(\d+)?\s*,?\s*((\d+)(/\d+)?)?\s*$'
% self._fmon_str, re.IGNORECASE)
self._ftext2 = re.compile(r'(\d+)?\s+?%s\s*((\d+)(/\d+)?)?\s*$'
% self._fmon_str, re.IGNORECASE)
self._ptext = re.compile(r'%s\s+(\d+)?\s*,?\s*((\d+)(/\d+)?)?\s*$'
% self._pmon_str, re.IGNORECASE)
self._ptext2 = re.compile(r'(\d+)?\s+?%s\s*((\d+)(/\d+)?)?\s*$'
% self._pmon_str, re.IGNORECASE)
self._itext = re.compile(r'%s\s+(\d+)?\s*,?\s*((\d+)(/\d+)?)?\s*$'
% self._imon_str, re.IGNORECASE)
self._itext2 = re.compile(r'(\d+)?\s+?%s\s*((\d+)(/\d+)?)?\s*$'
% self._imon_str, re.IGNORECASE)
self._stext = re.compile(r'%s\.?\s+(\d+)?\s*,?\s*((\d+)(/\d+)?)?\s*$'
% self._smon_str, re.IGNORECASE)
self._stext2 = re.compile(r'(\d+)?\s+?%s\.?\s*((\d+)(/\d+)?)?\s*$'
% self._smon_str, re.IGNORECASE)
self._numeric = re.compile(
r"((\d+)[/\.]\s*)?((\d+)[/\.]\s*)?(\d+)\s*$")
self._iso = re.compile(r"(\d+)(/(\d+))?-(\d+)-(\d+)\s*$")
self._isotimestamp = re.compile(
r"^\s*?(\d{4})([01]\d)([0123]\d)(?:(?:[012]\d[0-5]\d[0-5]\d)|"
r"(?:\s+[012]\d:[0-5]\d(?::[0-5]\d)?))?\s*?$")
self._rfc = re.compile(
r"(%s,)?\s+(\d|\d\d)\s+%s\s+(\d+)\s+\d\d:\d\d(:\d\d)?\s+"
r"(\+|-)\d\d\d\d" % (self._rfc_day_str, self._rfc_mon_str))
self._today = re.compile(r"^\s*%s\s*$" % self._today_str,
re.IGNORECASE)
def _get_int(self, val):
"""
+2 -1
View File
@@ -216,8 +216,9 @@ class DummyDb(M_A_M_B("NewBaseClass", (DbReadBase, Callback, object,), {})):
"""
Create a new DummyDb instance.
"""
DbReadBase.__init__(self)
Callback.__init__(self)
self.basedb = None
self.__feature = {} # {"feature": VALUE, ...}
self.db_is_open = False
self.readonly = True
self.name_formats = []
+7 -7
View File
@@ -1059,13 +1059,13 @@ class NameDisplay:
format_str = format_str[1:]
else:
patterns = [
",\W*\"%(" + ("|".join(codes)) + ")\"", # ,\W*"%s"
",\W*\(%(" + ("|".join(codes)) + ")\)", # ,\W*(%s)
",\W*%(" + ("|".join(codes)) + ")", # ,\W*%s
"\"%(" + ("|".join(codes)) + ")\"", # "%s"
"_%(" + ("|".join(codes)) + ")_", # _%s_
"\(%(" + ("|".join(codes)) + ")\)", # (%s)
"%(" + ("|".join(codes)) + ")", # %s
",\\W*\"%(" + ("|".join(codes)) + ")\"", # ,\W*"%s"
",\\W*\\(%(" + ("|".join(codes)) + ")\\)", # ,\W*(%s)
",\\W*%(" + ("|".join(codes)) + ")", # ,\W*%s
"\"%(" + ("|".join(codes)) + ")\"", # "%s"
"_%(" + ("|".join(codes)) + ")_", # _%s_
"\\(%(" + ("|".join(codes)) + ")\\)", # (%s)
"%(" + ("|".join(codes)) + ")", # %s
]
new_fmt = format_str
+3 -2
View File
@@ -35,7 +35,8 @@ import xml.dom.minidom
# Gramps modules
#
#-------------------------------------------------------------------------
from ..const import PLACE_FORMATS
from ..const import PLACE_FORMATS, GRAMPS_LOCALE as glocale
_ = glocale.translation.gettext
from ..config import config
from ..utils.location import get_location_list
from ..lib import PlaceType
@@ -72,7 +73,7 @@ class PlaceDisplay:
if os.path.exists(PLACE_FORMATS):
self.load_formats()
else:
pf = PlaceFormat('Full', ':', '', 0, False)
pf = PlaceFormat(_('Full'), ':', '', 0, False)
self.place_formats.append(pf)
def display_event(self, db, event, fmt=-1):
@@ -56,17 +56,17 @@ class ChangedSinceBase(Rule):
category = _('General filters')
def add_time(self, date):
if re.search("\d.*\s+\d{1,2}:\d{2}:\d{2}", date):
if re.search(r"\d.*\s+\d{1,2}:\d{2}:\d{2}", date):
return date
elif re.search("\d.*\s+\d{1,2}:\d{2}", date):
elif re.search(r"\d.*\s+\d{1,2}:\d{2}", date):
return date + ":00"
elif re.search("\d.*\s+\d{1,2}", date):
elif re.search(r"\d.*\s+\d{1,2}", date):
return date + ":00:00"
elif re.search("\d{4}-\d{1,2}-\d{1,2}", date):
elif re.search(r"\d{4}-\d{1,2}-\d{1,2}", date):
return date + " 00:00:00"
elif re.search("\d{4}-\d{1,2}", date):
elif re.search(r"\d{4}-\d{1,2}", date):
return date + "-01 00:00:00"
elif re.search("\d{4}", date):
elif re.search(r"\d{4}", date):
return date + "-01-01 00:00:00"
else:
return date
+4 -4
View File
@@ -520,11 +520,11 @@ class Place(CitationBase, NoteBase, MediaBase, UrlBase, PrimaryObject):
Return a list of alternate :class:`~.location.Location` objects the
present alternate information about the current Place.
A Place can have more than one :class:`~.location.Location`, since names
and jurisdictions can change over time for the same place.
A Place can have more than one :class:`~.location.Location`, since
names and jurisdictions can change over time for the same place.
:returns: Returns the alternate :class:`~.location.Location`\ s for the
Place
:returns: Returns the alternate :class:`~.location.Location` objects
for the Place
:rtype: list of :class:`~.location.Location` objects
"""
return self.alt_loc
+10 -2
View File
@@ -164,8 +164,16 @@ class MergeFamilyQuery:
self.phoenix = self.database.get_family_from_handle(new_handle)
self.titanic = self.database.get_family_from_handle(old_handle)
phoenix_father = self.database.get_person_from_handle(self.phoenix_fh)
phoenix_mother = self.database.get_person_from_handle(self.phoenix_mh)
if self.phoenix_fh:
phoenix_father = self.database.get_person_from_handle(
self.phoenix_fh)
else:
phoenix_father = None
if self.phoenix_mh:
phoenix_mother = self.database.get_person_from_handle(
self.phoenix_mh)
else:
phoenix_mother = None
self.phoenix = self.database.get_family_from_handle(new_handle)
self.titanic = self.database.get_family_from_handle(old_handle)
self.phoenix.merge(self.titanic)
+1 -1
View File
@@ -85,7 +85,7 @@ def _get_extension(mime_type):
extension = None
try:
hcr = ConnectRegistry(None, HKEY_CLASSES_ROOT)
subkey = OpenKey(hcr, "MIME\DataBase\Content Type")
subkey = OpenKey(hcr, r"MIME\DataBase\Content Type")
mimekey = OpenKey(subkey, mime_type)
extension, value_type = QueryValueEx(mimekey, "Extension")
CloseKey(mimekey)
+6 -2
View File
@@ -34,6 +34,7 @@ LOG = logging.getLogger(".Gramplets")
#
#------------------------------------------------------------------------
from ..const import GRAMPS_LOCALE as glocale
from gramps.gen.config import config
_ = glocale.translation.gettext
class Gramplet:
@@ -296,12 +297,15 @@ class Gramplet:
not self.gui.force_update):
self.dirty = True
if self.dbstate.is_open():
#print " %s is not active" % self.gui.gname
#print(" %s is not active" % self.gui.gname)
self.update_has_data()
else:
self.set_has_data(False)
return
#print " %s is UPDATING" % self.gui.gname
#print(" %s is UPDATING" % self.gui.gname)
if (config.get('interface.grampletbar-refresh') and
self.gui.view.navigation_type() != None): # dashboard has no navtype
return # Don't update the gramplet if we are in manual refresh
self.dirty = False
LOG.debug("gramplet updater: %s: running" % self.gui.title)
if self._idle_id != 0:
+14
View File
@@ -8,6 +8,7 @@
# Copyright (C) 2007 Brian G. Matherly
# Copyright (C) 2009 Benny Malengier
# Copyright (C) 2009 Gary Burton
# Copyright (C) 2017 Jonathan Biegert <azrdev@qrdn.de>
# Copyright (C) 2017 Mindaugas Baranauskas
# Copyright (C) 2017 Paul Culley
#
@@ -72,6 +73,11 @@ _RANKDIR = [{'name' : _("Vertical (↓)"), 'value' : "TB"},
{'name' : _("Horizontal (→)"), 'value' : "LR"},
{'name' : _("Horizontal (←)"), 'value' : "RL"}]
_NODE_PORTS = {"TB" : ("n", "s"),
"BT" : ("s", "n"),
"LR" : ("w", "e"),
"RL" : ("e", "w")}
_PAGEDIR = [{'name' : _("Bottom, left"), 'value' : "BL"},
{'name' : _("Bottom, right"), 'value' : "BR"},
{'name' : _("Top, left"), 'value' : "TL"},
@@ -183,6 +189,10 @@ class GVOptions:
spline.set_help(_("How the lines between objects will be drawn."))
menu.add_option(category, "spline", spline)
node_ports = BooleanOption(_("Alternate line attachment"), False)
node_ports.set_help(_("Whether lines attach to nodes differently"))
menu.add_option(category, "node_ports", node_ports)
# 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
@@ -409,6 +419,7 @@ class GVDocBase(BaseDoc, GVDoc):
self.vpages = get_option('v_pages').get_value()
self.usesubgraphs = get_option('usesubgraphs').get_value()
self.spline = get_option('spline').get_value()
self.node_ports = get_option('node_ports').get_value()
paper_size = paper_style.get_size()
@@ -456,6 +467,9 @@ class GVDocBase(BaseDoc, GVDoc):
' splines="%s";\n' % self.spline +
'\n' +
' edge [len=0.5 style=solid fontsize=%d];\n' % self.fontsize)
if self.node_ports:
self.write(' edge [headport=%s tailport=%s];\n'
% _NODE_PORTS[self.rankdir])
if self.fontfamily:
self.write(' node [style=filled fontname="%s" fontsize=%d];\n'
+1
View File
@@ -340,3 +340,4 @@ def add_place_format_option(menu, category):
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
+1 -1
View File
@@ -34,7 +34,7 @@ import unicodedata
# constants
#
#-------------------------------------------------------------------------
IGNORE = "HW~!@#$%^&*()_+=-`[]\|;:'/?.,<>\" \t\f\v"
IGNORE = "HW~!@#$%^&*()_+=-`[]\\|;:'/?.,<>\" \t\f\v"
TABLE = bytes.maketrans(b'ABCDEFGIJKLMNOPQRSTUVXYZ',
b'012301202245501262301202')
+1 -1
View File
@@ -256,7 +256,7 @@ def get_participant_from_event(db, event_handle, all_=False):
Obtain the first primary or family participant to an event we find in the
database. Note that an event can have more than one primary or
family participant, only one is returned, adding ellipses if there are
more. If the all\_ parameter is true a comma-space separated string with
more. If the all_ parameter is true a comma-space separated string with
the names of all primary participants is returned and no ellipses is used.
"""
participant = ""
+9 -3
View File
@@ -251,11 +251,17 @@ def create_checksum(full_path):
Create a md5 hash for the given file.
"""
full_path = os.path.normpath(full_path)
md5 = hashlib.md5()
try:
with open(full_path, 'rb') as media_file:
md5sum = hashlib.md5(media_file.read()).hexdigest()
while True:
buf = media_file.read(65536)
if not buf:
break
md5.update(buf)
md5sum = md5.hexdigest()
except IOError:
md5sum = ''
md5sum = ''
except UnicodeEncodeError:
md5sum = ''
md5sum = ''
return md5sum
+2 -2
View File
@@ -459,12 +459,12 @@ def run():
if argpars.need_gui():
LOG.debug("A GUI is needed, set it up")
try:
from .gui.grampsgui import startgtkloop
from .gui.grampsgui import startgramps
# no DISPLAY is a RuntimeError in an older pygtk (e.g. F14's 2.17)
except RuntimeError as msg:
error += [(_("Configuration error:"), str(msg))]
return error
startgtkloop(error, argpars)
startgramps(error, argpars)
else:
# CLI use of Gramps
argpars.print_help()
-45
View File
@@ -1,45 +0,0 @@
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2015 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.
#
"""
A replacement ActionGroup that correctly loads named icons from an icon theme.
"""
from gi.repository import Gtk
class ActionGroup(Gtk.ActionGroup):
def add_actions(self, action_list, **kwargs):
Gtk.ActionGroup.add_actions(self, action_list, **kwargs)
self.fix_icon_name(action_list)
def add_toggle_actions(self, action_list, **kwargs):
Gtk.ActionGroup.add_toggle_actions(self, action_list, **kwargs)
self.fix_icon_name(action_list)
def add_radio_actions(self, action_list, **kwargs):
Gtk.ActionGroup.add_radio_actions(self, action_list, **kwargs)
self.fix_icon_name(action_list)
def fix_icon_name(self, action_list):
for action_tuple in action_list:
if action_tuple[1]:
action = self.get_action(action_tuple[0])
action.set_icon_name(action_tuple[1])
+4 -2
View File
@@ -143,8 +143,10 @@ class ColumnOrder(Gtk.Box):
index = 0
for val, size in zip(self.oldorder, self.oldsize):
if val in self.oldvis:
size = widths[index]
index += 1
if val != self.oldvis[-1]:
# don't use last col width, its wrong
size = widths[index]
index += 1
colord.append((1, val, size))
else:
colord.append((0, val, size))
+13
View File
@@ -984,6 +984,12 @@ class GrampsPreferences(ConfigureDialog):
"""
self.uistate.emit('grampletbar-close-changed')
def cb_grampletbar_refresh(self, obj):
"""
Gramplet bar refresh button preference callback
"""
self.uistate.emit('grampletbar-refresh-changed')
def add_formats_panel(self, configdialog):
row = 0
grid = Gtk.Grid()
@@ -1185,6 +1191,13 @@ class GrampsPreferences(ConfigureDialog):
row, 'interface.grampletbar-close', stop=3,
extra_callback=self.cb_grampletbar_close)
row += 1
# Gramplet bar refresh button:
self.add_checkbox(grid,
_("Enable manual refresh for the active gramplet"),
row, 'interface.grampletbar-refresh', stop=3,
extra_callback=self.cb_grampletbar_refresh)
row += 1
return _('Display'), grid
def auto_title_changed(self, obj):
+2 -2
View File
@@ -1048,8 +1048,8 @@ def find_revisions(name):
"""
import re
rev = re.compile("\s*revision\s+([\d\.]+)")
date = re.compile("date:\s+(\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d)[-+]\d\d;")
rev = re.compile(r"\s*revision\s+([\d\.]+)")
date = re.compile(r"date:\s+(\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d)[-+]\d\d;")
if not os.path.isfile(name) or not _RCS_FOUND:
return []
+49 -29
View File
@@ -28,6 +28,7 @@
#-------------------------------------------------------------------------
import os
from io import StringIO
import html
#-------------------------------------------------------------------------
#
@@ -65,6 +66,7 @@ from .glade import Glade
from gramps.gen.utils.db import navigation_label
from .widgets.progressdialog import ProgressMonitor, GtkProgressDialog
from .dialog import ErrorDialog
from .uimanager import ActionGroup
DISABLED = -1
@@ -246,14 +248,34 @@ class History(Callback):
#
#-------------------------------------------------------------------------
_RCT_TOP = '<ui><menubar name="MenuBar"><menu action="FileMenu"><menu action="OpenRecent">'
_RCT_BTM = '</menu></menu></menubar></ui>'
_RCT_TOP = '<placeholder id="OpenRecentMenu">'
_RCT_MENU = '''
<item>
<attribute name="action">win.%s</attribute>
<attribute name="label" translatable="no">%s</attribute>
</item>'''
_RCT_BTM = '\n </placeholder>\n'
_RCT_BAR_TOP = ('<object class="GtkMenu" id="OpenBtnMenu">\n'
'<property name="visible">True</property>\n'
'<property name="can_focus">False</property>')
_RCT_BAR = '''
<child>
<object class="GtkMenuItem">
<property name="action-name">win.%s</property>
<property name="label" translatable="yes">%s</property>
<property name="use_underline">False</property>
<property name="visible">True</property>
</object>
</child>'''
_RCT_BAR_BTM = '\n</object>\n'
from gramps.gen.recentfiles import RecentFiles
class RecentDocsMenu:
def __init__(self, uistate, state, fileopen):
self.action_group = Gtk.ActionGroup(name='RecentFiles')
self.ui_xml = []
self.action_group = ActionGroup('RecentFiles')
self.active = DISABLED
self.uistate = uistate
self.uimanager = uistate.uimanager
@@ -268,55 +290,52 @@ class RecentDocsMenu:
ErrorDialog(_('Cannot load database'), str(err),
parent=self.uistate.window)
def build(self):
buf = StringIO()
buf.write(_RCT_TOP)
def build(self, update_menu=True):
gramps_rf = RecentFiles()
count = 0
if self.active != DISABLED:
self.uimanager.remove_ui(self.active)
self.uimanager.remove_ui(self.ui_xml)
self.uimanager.remove_action_group(self.action_group)
self.action_group = Gtk.ActionGroup(name='RecentFiles')
self.active = DISABLED
actions = []
actionlist = []
menu = _RCT_TOP
bar = _RCT_BAR_TOP
rfiles = gramps_rf.gramps_recent_files
rfiles.sort(key=lambda x: x.get_time(), reverse=True)
new_menu = Gtk.Menu()
#new_menu = Gtk.Menu()
#new_menu.set_tooltip_text(_("Connect to a recent database"))
for item in rfiles:
try:
title = item.get_name()
title = html.escape(item.get_name())
filename = os.path.basename(item.get_path())
action_id = "RecentMenu%d" % count
buf.write('<menuitem action="%s"/>' % action_id)
actions.append((action_id, None, title, None, None,
make_callback(item, self.load)))
mitem = Gtk.MenuItem(label=title, use_underline=False)
mitem.connect('activate', make_callback(item, self.load))
mitem.show()
new_menu.append(mitem)
# add the menuitem for this file
menu += _RCT_MENU % (action_id, title)
# add the action for this file
actionlist.append((action_id, make_callback(item, self.load)))
# add the toolbar menuitem
bar += _RCT_BAR % (action_id, title)
except RuntimeError:
# ignore no longer existing files
_LOG.info("Ignoring the RecentItem %s (%s)" % (title, filename))
count += 1
buf.write(_RCT_BTM)
self.action_group.add_actions(actions)
self.uimanager.insert_action_group(self.action_group, 1)
self.active = self.uimanager.add_ui_from_string(buf.getvalue())
self.uimanager.ensure_update()
buf.close()
if len(rfiles) > 0:
new_menu.show()
self.uistate.set_open_recent_menu(new_menu)
menu += _RCT_BTM
bar += _RCT_BAR_BTM
self.ui_xml = [menu, bar]
self.action_group.add_actions(actionlist)
self.uimanager.insert_action_group(self.action_group)
self.active = self.uimanager.add_ui_from_string(self.ui_xml)
if update_menu:
self.uimanager.update_menu()
def make_callback(val, func):
return lambda x: func(val)
return lambda x, y: func(val)
from .logger import RotateHandler
@@ -379,6 +398,7 @@ class DisplayState(Callback):
'nameformat-changed' : None,
'placeformat-changed' : None,
'grampletbar-close-changed' : None,
'grampletbar-refresh-changed' : None,
'update-available' : (list, ),
'autobackup' : None,
}
+1 -1
View File
@@ -142,7 +142,7 @@ class EditLink(ManagedWindow):
def update_ui(self, widget):
url = self.url_link.get_text()
# text needs to have 3 or more chars://and at least one char
match = re.match("\w{3,}://\w+", url)
match = re.match(r"\w{3,}://\w+", url)
if match:
self.ok_button.set_sensitive(True)
else:
+3 -2
View File
@@ -180,7 +180,6 @@ class EditNote(EditPrimary):
self.set_window(win, None, self.get_menu_title())
self.setup_configs('interface.note', 700, 500)
vboxnote = self.top.get_object('vbox131')
notebook = self.top.get_object('note_notebook')
#recreate start page as GrampsTab
@@ -271,7 +270,9 @@ class EditNote(EditPrimary):
# create a formatting toolbar
if not self.dbstate.db.readonly:
vbox = self.top.get_object('container')
vbox.pack_start(self.texteditor.get_toolbar(), False, False, 0)
toolbar, self.action_group = self.texteditor.create_toolbar(
self.uistate.uimanager, self.window)
vbox.pack_start(toolbar, False, False, 0)
self.texteditor.set_transient_parent(self.window)
# setup initial values for textview and textbuffer
+28 -24
View File
@@ -676,33 +676,33 @@ class EditPerson(EditPrimary):
EditMediaRef(self.dbstate, self.uistate, self.track,
media_obj, media_ref, self.load_photo)
def _top_contextmenu(self):
def _top_contextmenu(self, prefix):
"""
Override from base class, the menuitems and actiongroups for the top
of context menu.
"""
self.all_action = Gtk.ActionGroup(name="/PersonAll")
self.home_action = Gtk.ActionGroup(name="/PersonHome")
self.track_ref_for_deletion("all_action")
self.track_ref_for_deletion("home_action")
if self.added:
# Don't add items if not a real person yet
return '', []
self.all_action.add_actions([
('ActivePerson', None, _("Make Active Person"),
None, None, self._make_active),
])
self.home_action.add_actions([
('HomePerson', 'go-home', _("Make Home Person"),
None, None, self._make_home_person),
])
_actions = [('ActivePerson', self._make_active),
('HomePerson', self._make_home_person)]
self.all_action.set_visible(not self.added)
self.home_action.set_visible(not self.added)
ui_top_cm = (
'''
<item>
<attribute name="action">{prefix}.ActivePerson</attribute>
<attribute name="label" translatable="yes">Make Active Person'''
'''</attribute>
</item>
<item>
<attribute name="action">{prefix}.HomePerson</attribute>
<attribute name="label" translatable="yes">Make Home Person'''
'''</attribute>
</item>
'''.format(prefix=prefix))
ui_top_cm = '''
<menuitem action="ActivePerson"/>
<menuitem action="HomePerson"/>'''
return ui_top_cm, [self.all_action, self.home_action]
return ui_top_cm, _actions
def _top_drag_data_get(self, widget, context, sel_data, info, time):
if info == DdTargets.PERSON_LINK.app_id:
@@ -713,17 +713,21 @@ class EditPerson(EditPrimary):
"""
Override base class, make inactive home action if not needed.
"""
if self.added:
return
home_action = self.uistate.uimanager.get_action(self.action_group,
'HomePerson')
if (self.dbstate.db.get_default_person() and
self.obj.get_handle() ==
self.dbstate.db.get_default_person().get_handle()):
self.home_action.set_sensitive(False)
home_action.set_enabled(False)
else:
self.home_action.set_sensitive(True)
home_action.set_enabled(True)
def _make_active(self, obj):
def _make_active(self, obj, value):
self.uistate.set_active(self.obj.get_handle(), 'Person')
def _make_home_person(self, obj):
def _make_home_person(self, obj, value):
handle = self.obj.get_handle()
if handle:
self.dbstate.db.set_default_person_handle(handle)
+2 -2
View File
@@ -179,8 +179,8 @@ class EditPlaceRef(EditReference):
self.latitude.set_text(value[:coma])
self.top.get_object("lat_entry").validate(force=True)
self.top.get_object("lon_entry").validate(force=True)
self.obj.set_latitude(self.latitude.get_value())
self.obj.set_longitude(self.longitude.get_value())
self.source.set_latitude(self.latitude.get_value())
self.source.set_longitude(self.longitude.get_value())
except:
pass
+45 -25
View File
@@ -32,6 +32,7 @@ import abc
#
#-------------------------------------------------------------------------
from gi.repository import Gtk
from gi.repository.Gio import SimpleActionGroup
#-------------------------------------------------------------------------
#
@@ -49,6 +50,8 @@ from ..display import display_help
from ..dialog import SaveDialog
from gramps.gen.lib import PrimaryObject
from ..dbguielement import DbGUIElement
from ..uimanager import ActionGroup
class EditPrimary(ManagedWindow, DbGUIElement, metaclass=abc.ABCMeta):
@@ -75,6 +78,7 @@ class EditPrimary(ManagedWindow, DbGUIElement, metaclass=abc.ABCMeta):
self.get_from_gramps_id = get_from_gramps_id
self.contexteventbox = None
self.__tabs = []
self.action_group = None
ManagedWindow.__init__(self, uistate, track, obj)
DbGUIElement.__init__(self, self.db)
@@ -184,6 +188,8 @@ class EditPrimary(ManagedWindow, DbGUIElement, metaclass=abc.ABCMeta):
self.dbstate.disconnect(self.dbstate_connect_key)
self._cleanup_connects()
self._cleanup_on_exit()
if self.action_group:
self.uistate.uimanager.remove_action_group(self.action_group)
self.get_from_handle = None
self.get_from_gramps_id = None
ManagedWindow.close(self)
@@ -283,48 +289,62 @@ class EditPrimary(ManagedWindow, DbGUIElement, metaclass=abc.ABCMeta):
return False
#build the possible popup menu
self._build_popup_ui()
menu_model = self._build_popup_ui()
if not menu_model:
return False
#set or unset sensitivity in popup
self._post_build_popup_ui()
menu = self.popupmanager.get_widget('/Popup')
if menu:
menu.popup(None, None, None, None, event.button, event.time)
return True
menu = Gtk.Menu.new_from_model(menu_model)
menu.attach_to_widget(obj, None)
menu.show_all()
if Gtk.MINOR_VERSION < 22:
# ToDo The following is reported to work poorly with Wayland
menu.popup(None, None, None, None,
event.button, event.time)
else:
menu.popup_at_pointer(event)
return True
return False
def _build_popup_ui(self):
"""
Create actions and ui of context menu
If you don't need a popup, override this and return None
"""
from ..plug.quick import create_quickreport_menu
self.popupmanager = Gtk.UIManager()
#add custom actions
(ui_top, action_groups) = self._top_contextmenu()
for action in action_groups :
self.popupmanager.insert_action_group(action, -1)
prefix = str(id(self))
#get custom ui and actions
(ui_top, actions) = self._top_contextmenu(prefix)
#see which quick reports are available now:
ui_qr = ''
if self.QR_CATEGORY > -1 :
(ui_qr, reportactions) = create_quickreport_menu(self.QR_CATEGORY,
self.dbstate, self.uistate,
self.obj, track=self.track)
self.report_action = Gtk.ActionGroup(name="/PersonReport")
self.report_action.add_actions(reportactions)
self.report_action.set_visible(True)
self.popupmanager.insert_action_group(self.report_action, -1)
(ui_qr, reportactions) = create_quickreport_menu(
self.QR_CATEGORY, self.dbstate, self.uistate,
self.obj, prefix, track=self.track)
actions.extend(reportactions)
popupui = '''
<ui>
<popup name="Popup">''' + ui_top + '''
<separator/>''' + ui_qr + '''
</popup>
</ui>'''
popupui = '''<?xml version="1.0" encoding="UTF-8"?>
<interface>
<menu id="Popup">''' + ui_top + '''
<section>
''' + ui_qr + '''
</section>
</menu>
</interface>'''
self.popupmanager.add_ui_from_string(popupui)
builder = Gtk.Builder.new_from_string(popupui, -1)
def _top_contextmenu(self):
self.action_group = ActionGroup('EditPopup' + prefix, actions,
prefix)
act_grp = SimpleActionGroup()
self.window.insert_action_group(prefix, act_grp)
self.window.set_application(self.uistate.uimanager.app)
self.uistate.uimanager.insert_action_group(self.action_group, act_grp)
return builder.get_object('Popup')
def _top_contextmenu(self, prefix):
"""
Derived class can create a ui with menuitems and corresponding list of
actiongroups
@@ -186,8 +186,10 @@ class PersonSidebarFilter(SidebarFilter):
# if the name is not empty, choose either the regular expression
# version or the normal text match
if name:
rule = RegExpName([name], use_regex=regex)
generic_filter.add_rule(rule)
name_parts = name.split(sep=" ")
for name_part in name_parts:
rule = RegExpName([name_part], use_regex=regex)
generic_filter.add_rule(rule)
# if the id is not empty, choose either the regular expression
# version or the normal text match
+452 -70
View File
@@ -3,6 +3,7 @@
#
# Copyright (C) 2000-2006 Donald N. Allingham
# Copyright (C) 2009 Benny Malengier
# Copyright (C) 2018 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
@@ -50,7 +51,369 @@ _ = glocale.translation.gettext
MIN_PYGOBJECT_VERSION = (3, 12, 0)
PYGOBJ_ERR = False
MIN_GTK_VERSION = (3, 10)
MIN_GTK_VERSION = (3, 12)
UIDEFAULT = (
'''<?xml version="1.0" encoding="UTF-8"?>
<interface>
<menu id="menubar"><section id="menubar-update">
<submenu id='m1'>
<attribute name="label" translatable="yes">_Family Trees</attribute>
<section id="ftree">
<item>
<attribute name="action">win.Open</attribute>
<attribute name="label" translatable="yes">'''
'''_Manage Family Trees</attribute>
</item>
<submenu>
<attribute name="action">win.OpenRecent</attribute>
<attribute name="label" translatable="yes">Open _Recent</attribute>
<placeholder id="OpenRecentMenu">
</placeholder>
</submenu>
<item groups='RO'>
<attribute name="action">win.Close</attribute>
<attribute name="label" translatable="yes">_Close</attribute>
</item>
</section>
<section groups='RO RW'>
<item groups='RW'>
<attribute name="action">win.Import</attribute>
<attribute name="label" translatable="yes">_Import...</attribute>
</item>
<item>
<attribute name="action">win.Export</attribute>
<attribute name="label" translatable="yes">_Export...</attribute>
</item>
<placeholder id="LocalExport">
</placeholder>
<item>
<attribute name="action">win.Backup</attribute>
<attribute name="label" translatable="yes">Make Backup...</attribute>
</item>
</section>
<section>
<item groups='RO'>
<attribute name="action">win.Abandon</attribute>
<attribute name="label" translatable="yes">'''
'''_Abandon Changes and Quit</attribute>
</item>
<placeholder groups='OSX' id='osxquit'>
<item>
<attribute name="action">app.quit</attribute>
<attribute name="label" translatable="yes">_Quit</attribute>
</item>
</placeholder>
</section>
</submenu>
<submenu id='m2' groups='RW'>
<attribute name="label" translatable="yes">_Add</attribute>
<section>
<item>
<attribute name="action">win.PersonAdd</attribute>
<attribute name="label" translatable="yes">Person</attribute>
</item>
</section>
<section>
<item>
<attribute name="action">win.FamilyAdd</attribute>
<attribute name="label" translatable="yes">Family</attribute>
</item>
</section>
<section>
<item>
<attribute name="action">win.EventAdd</attribute>
<attribute name="label" translatable="yes">Event</attribute>
</item>
</section>
<section>
<item>
<attribute name="action">win.PlaceAdd</attribute>
<attribute name="label" translatable="yes">Place</attribute>
</item>
<item>
<attribute name="action">win.SourceAdd</attribute>
<attribute name="label" translatable="yes">Source</attribute>
</item>
<item>
<attribute name="action">win.CitationAdd</attribute>
<attribute name="label" translatable="yes">Citation</attribute>
</item>
<item>
<attribute name="action">win.RepositoryAdd</attribute>
<attribute name="label" translatable="yes">Repository</attribute>
</item>
<item>
<attribute name="action">win.MediaAdd</attribute>
<attribute name="label" translatable="yes">Media</attribute>
</item>
<item>
<attribute name="action">win.NoteAdd</attribute>
<attribute name="label" translatable="yes">Note</attribute>
</item>
</section>
</submenu>
<submenu id='m3'>
<attribute name="label" translatable="yes">_Edit</attribute>
<section groups='RW'>
<placeholder id="undo">
</placeholder>
<placeholder id="redo">
</placeholder>
<item>
<attribute name="action">win.UndoHistory</attribute>
<attribute name="label" translatable="yes">Undo History</attribute>
</item>
</section>
<section id='CommonEdit' groups='RW'>
</section>
<section id='TagMenu' groups='RW'>
</section>
<section groups='RW'>
<item>
<attribute name="action">win.Clipboard</attribute>
<attribute name="label" translatable="yes">Clip_board</attribute>
</item>
</section>
<section>
<placeholder groups='OSX' id='osxpref'>
<item>
<attribute name="action">app.preferences</attribute>
<attribute name="label" translatable="yes">'''
'''_Preferences...</attribute>
</item>
</placeholder>
<placeholder id='otheredit'>
</placeholder>
</section>
</submenu>
<submenu id='m4'>
<attribute name="label" translatable="yes">_View</attribute>
<section>
<item>
<attribute name="action">win.ConfigView</attribute>
<attribute name="label" translatable="yes">_Configure...</attribute>
</item>
<item>
<attribute name="action">win.Navigator</attribute>
<attribute name="label" translatable="yes">_Navigator</attribute>
</item>
<item>
<attribute name="action">win.Toolbar</attribute>
<attribute name="label" translatable="yes">_Toolbar</attribute>
</item>
<placeholder id='Bars'>
</placeholder>
<item>
<attribute name="action">win.Fullscreen</attribute>
<attribute name="label" translatable="yes">F_ull Screen</attribute>
</item>
</section>
<section id="ViewsInCatagory">
</section>
</submenu>
<submenu id="m5" groups='RO'>
<attribute name="label" translatable="yes">_Go</attribute>
<placeholder id="CommonGo">
</placeholder>
<section id="CommonHistory">
</section>
</submenu>
<submenu id='m6' groups='RW'>
<attribute name="label" translatable="yes">_Bookmarks</attribute>
<section id="AddEditBook">
</section>
<section id="GoToBook">
</section>
</submenu>
<submenu id='m7' groups='RO'>
<attribute name="label" translatable="yes">_Reports</attribute>
<section>
<item>
<attribute name="action">win.Books</attribute>
<attribute name="label" translatable="yes">Books...</attribute>
</item>
</section>
<section id="P_ReportsMenu">
</section>
</submenu>
<submenu id='m8' groups='RW'>
<attribute name="label" translatable="yes">_Tools</attribute>
<section id="P_ToolsMenu">
</section>
</submenu>
<submenu id='m9' groups='RO'>
<attribute name="label" translatable="yes">_Windows</attribute>
<section id="WinMenu">
</section>
</submenu>
<submenu id='m10'>
<attribute name="label" translatable="yes">_Help</attribute>
<section>
<item>
<attribute name="action">win.UserManual</attribute>
<attribute name="label" translatable="yes">_User Manual</attribute>
</item>
<item>
<attribute name="action">win.FAQ</attribute>
<attribute name="label" translatable="yes">_FAQ</attribute>
</item>
<item>
<attribute name="action">win.KeyBindings</attribute>
<attribute name="label" translatable="yes">_Key Bindings</attribute>
</item>
<item>
<attribute name="action">win.TipOfDay</attribute>
<attribute name="label" translatable="yes">Tip of the Day</attribute>
</item>
<item>
<attribute name="action">win.PluginStatus</attribute>
<attribute name="label" translatable="yes">'''
'''_Plugin Manager</attribute>
</item>
</section>
<section>
<item>
<attribute name="action">win.HomePage</attribute>
<attribute name="label" translatable="yes">'''
'''Gramps _Home Page</attribute>
</item>
<item>
<attribute name="action">win.MailingLists</attribute>
<attribute name="label" translatable="yes">'''
'''Gramps _Mailing Lists</attribute>
</item>
<item>
<attribute name="action">win.ReportBug</attribute>
<attribute name="label" translatable="yes">_Report a Bug</attribute>
</item>
<item>
<attribute name="action">win.ExtraPlugins</attribute>
<attribute name="label" translatable="yes">'''
'''_Extra Reports/Tools</attribute>
</item>
</section>
<section groups='OSX'>
<item>
<attribute name="action">app.about</attribute>
<attribute name="label" translatable="yes">_About</attribute>
</item>
</section>
</submenu>
</section></menu>
<object class="GtkMenu" id="OpenBtnMenu">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<object class="GtkToolbar" id="ToolBar">
<property name="hexpand">1</property>
<property name="toolbar-style">GTK_TOOLBAR_ICONS</property>
<style>
<class name="primary-toolbar"/>
</style>
<child>
<object class="GtkToolButton">
<property name="icon-name">gramps</property>
<property name="action-name">win.Open</property>
<property name="tooltip_text" translatable="yes">'''
'''Manage databases</property>
<property name="label" translatable="yes">_Family Trees</property>
<property name="use-underline">True</property>
</object>
<packing>
<property name="homogeneous">False</property>
</packing>
</child>
<child>
<object class="GtkToolItem">
<child>
<object class="GtkMenuButton">
<property name="tooltip_text" translatable="yes">'''
'''Connect to a recent database</property>
<property name="popup">OpenBtnMenu</property>
<property name="relief">GTK_RELIEF_NONE</property>
</object>
</child>
</object>
</child>
<placeholder id='CommonNavigation'>
</placeholder>
<placeholder id='BarCommonEdit'>
</placeholder>
<placeholder id='TagTool'>
</placeholder>
<child groups='RW'>
<object class="GtkToolButton" id="Clipboard">
<property name="icon-name">edit-paste</property>
<property name="action-name">win.Clipboard</property>
<property name="tooltip_text" translatable="yes">'''
'''Open the Clipboard dialog</property>
<property name="label" translatable="yes">Clip_board</property>
<property name="use-underline">True</property>
</object>
<packing>
<property name="homogeneous">False</property>
</packing>
</child>
<child>
<object class="GtkSeparatorToolItem"/>
</child>
<child>
<object class="GtkToolButton" id="ConfigView">
<property name="icon-name">gramps-config</property>
<property name="action-name">win.ConfigView</property>
<property name="tooltip_text" translatable="yes">'''
'''Configure the active view</property>
<property name="label" translatable="yes">_Configure...</property>
<property name="use-underline">True</property>
</object>
<packing>
<property name="homogeneous">False</property>
</packing>
</child>
<placeholder id='ViewsInCategoryBar'>
</placeholder>
<child>
<object class="GtkSeparatorToolItem" id="sep"/>
</child>
<placeholder id="MoreButtons">
</placeholder>
<child groups='RO'>
<object class="GtkToolButton" id="Reports">
<property name="icon-name">gramps-reports</property>
<property name="action-name">win.Reports</property>
<property name="tooltip_text" translatable="yes">'''
'''Open the reports dialog</property>
<property name="label" translatable="yes">_Reports</property>
<property name="use-underline">True</property>
</object>
<packing>
<property name="homogeneous">False</property>
</packing>
</child>
<child groups='RW'>
<object class="GtkToolButton" id="Tools">
<property name="icon-name">gramps-tools</property>
<property name="action-name">win.Tools</property>
<property name="tooltip_text" translatable="yes">'''
'''Open the tools dialog</property>
<property name="label" translatable="yes">_Tools</property>
<property name="use-underline">True</property>
</object>
<packing>
<property name="homogeneous">False</property>
</packing>
</child>
<placeholder id='AfterTools'>
</placeholder>
</object>
<menu id="Popup">
</menu>
</interface>
''')
try:
#import gnome introspection, part of pygobject
@@ -235,9 +598,8 @@ class Gramps:
process. It may spawn several windows and control several databases.
"""
def __init__(self, argparser):
def __init__(self, argparser, app):
from gramps.gen.dbstate import DbState
from . import viewmanager
from .viewmanager import ViewManager
from gramps.cli.arghandler import ArgHandler
from .tipofday import TipOfDay
@@ -248,7 +610,7 @@ class Gramps:
theme.append_search_path(IMAGE_DIR)
dbstate = DbState()
self._vm = ViewManager(dbstate,
self._vm = ViewManager(app, dbstate,
config.get("interface.view-categories"))
if (lin()
@@ -256,7 +618,7 @@ class Gramps:
and not gettext.find(GTK_GETTEXT_DOMAIN)):
_display_gtk_gettext_message(parent=self._vm.window)
#_display_welcome_message(parent=self._vm.window)
_display_welcome_message(parent=self._vm.window)
_display_translator_message(parent=self._vm.window)
@@ -303,68 +665,18 @@ class Gramps:
#
#-------------------------------------------------------------------------
def __startgramps(errors, argparser):
def startgramps(errors, argparser):
"""
Main startup function started via GObject.timeout_add
First action inside the gtk loop
"""
try:
from .dialog import ErrorDialog
#handle first existing errors in GUI fashion
if errors:
for error in errors:
ErrorDialog(error[0], error[1]) # TODO no-parent
Gtk.main_quit()
sys.exit(1)
if argparser.errors:
for error in argparser.errors:
ErrorDialog(error[0], error[1]) # TODO no-parent
Gtk.main_quit()
sys.exit(1)
# add gui logger
from .logger import RotateHandler, GtkHandler
form = logging.Formatter(
fmt="%(relativeCreated)d: %(levelname)s: "
"%(filename)s: line %(lineno)d: %(message)s")
# Create the log handlers
rot_h = RotateHandler(capacity=20)
rot_h.setFormatter(form)
# Only error and critical log records should
# trigger the GUI handler.
gtkh = GtkHandler(rotate_handler=rot_h)
gtkh.setFormatter(form)
gtkh.setLevel(logging.ERROR)
logger = logging.getLogger()
logger.addHandler(rot_h)
logger.addHandler(gtkh)
except:
#make sure there is a clean exit if there is an error in above steps
quit_now = True
exit_code = 1
LOG.error(_("\nGramps failed to start. "
"Please report a bug about this.\n"
"This could be because of an error "
"in a (third party) View on startup.\n"
"To use another view, don't load a Family Tree, "
"change view, and then load your Family Tree.\n"
"You can also change manually "
"the startup view in the gramps.ini file \n"
"by changing the last-view parameter.\n"
), exc_info=True)
# start Gramps, errors stop the gtk loop
app = GrampsApplication(errors, argparser)
try:
quit_now = False
exit_code = 0
if has_display():
Gramps(argparser)
else:
if app.run():
print(_("Gramps terminated because of no DISPLAY"))
quit_now = True
exit_code = 1
except SystemExit as err:
quit_now = True
if err.code:
@@ -395,18 +707,88 @@ def __startgramps(errors, argparser):
), exc_info=True)
if quit_now:
#stop gtk loop and quit
Gtk.main_quit()
app.quit()
sys.exit(exit_code)
#function finished, return False to stop the timeout_add function calls
return False
def startgtkloop(errors, argparser):
"""
We start the gtk loop and run the function to start up Gramps
"""
GLib.timeout_add(100, __startgramps, errors, argparser, priority=100)
if os.path.exists(os.path.join(DATA_DIR, "gramps.accel")):
Gtk.AccelMap.load(os.path.join(DATA_DIR, "gramps.accel"))
Gtk.main()
# we do the following import here to avoid the Gtk require version warning
from .uimanager import UIManager
from gramps.gen.constfunc import is_quartz
class GrampsApplication(Gtk.Application):
def __init__(self, errors, argparser):
super().__init__(application_id="org.gramps-project.Gramps")
self.window = None
self.errors = errors
self.argparser = argparser
def do_startup(self):
Gtk.Application.do_startup(self)
self.uimanager = UIManager(self, UIDEFAULT)
if not is_quartz():
self.uimanager.show_groups = ['OSX']
self.uimanager.update_menu(init=True)
if os.path.exists(os.path.join(DATA_DIR, "gramps.accel")):
self.uimanager.load_accels(os.path.join(DATA_DIR, "gramps.accel"))
try:
from .dialog import ErrorDialog
#handle first existing errors in GUI fashion
if self.errors:
for error in self.errors:
ErrorDialog(error[0], error[1]) # TODO no-parent
Gtk.main_quit()
sys.exit(1)
if self.argparser.errors:
for error in self.argparser.errors:
ErrorDialog(error[0], error[1]) # TODO no-parent
Gtk.main_quit()
sys.exit(1)
# add gui logger
from .logger import RotateHandler, GtkHandler
form = logging.Formatter(
fmt="%(relativeCreated)d: %(levelname)s: "
"%(filename)s: line %(lineno)d: %(message)s")
# Create the log handlers
rot_h = RotateHandler(capacity=20)
rot_h.setFormatter(form)
# Only error and critical log records should
# trigger the GUI handler.
gtkh = GtkHandler(rotate_handler=rot_h)
gtkh.setFormatter(form)
gtkh.setLevel(logging.ERROR)
logger = logging.getLogger()
logger.addHandler(rot_h)
logger.addHandler(gtkh)
except:
#make sure there is a clean exit if error in above steps
exit_code = 1
LOG.error(_("\nGramps failed to start. "
"Please report a bug about this.\n"
"This could be because of an error "
"in a (third party) View on startup.\n"
"To use another view, don't load a Family Tree, "
"change view, and then load your Family Tree.\n"
"You can also change manually "
"the startup view in the gramps.ini file \n"
"by changing the last-view parameter.\n"
), exc_info=True)
#stop gtk loop and quit
self.quit()
sys.exit(exit_code)
def do_activate(self):
# We only allow a single window and raise any existing ones
if not self.window:
# Windows are associated with the application
# when the last one is closed the application shuts down
Gramps(self.argparser, self)
else:
print('Gramps is already running.')
self.window.present()
+25 -23
View File
@@ -31,7 +31,7 @@ the create/deletion of dialog windows.
#-------------------------------------------------------------------------
import os
from io import StringIO
import html
#-------------------------------------------------------------------------
#
# GNOME/GTK
@@ -49,6 +49,7 @@ from gramps.gen.const import GLADE_FILE, ICON
from gramps.gen.errors import WindowActiveError
from gramps.gen.config import config
from gramps.gen.constfunc import is_quartz
from .uimanager import ActionGroup
from .glade import Glade
#-------------------------------------------------------------------------
@@ -57,8 +58,8 @@ from .glade import Glade
#
#-------------------------------------------------------------------------
_win_top = '<ui><menubar name="MenuBar"><menu action="WindowsMenu">'
_win_btm = '</menu></menubar></ui>'
_win_top = '<section id="WinMenu">\n'
_win_btm = '</section>\n'
DISABLED = -1
#-----------------------------------------------------------------------
@@ -108,7 +109,7 @@ class GrampsWindowManager:
self.uimanager = uimanager
self.window_tree = []
self.id2item = {}
self.action_group = Gtk.ActionGroup(name='WindowManger')
self.action_group = ActionGroup(name='WindowManger')
self.active = DISABLED
self.ui = _win_top + _win_btm
@@ -125,9 +126,9 @@ class GrampsWindowManager:
"""
Enables the UI and action groups
"""
self.uimanager.insert_action_group(self.action_group, 1)
self.active = self.uimanager.add_ui_from_string(self.ui)
self.uimanager.ensure_update()
self.uimanager.insert_action_group(self.action_group)
self.active = self.uimanager.add_ui_from_string([self.ui])
self.uimanager.update_menu()
def get_item_from_track(self, track):
# Recursively find an item given track sequence
@@ -270,31 +271,35 @@ class GrampsWindowManager:
def call_back_factory(self, item):
if not isinstance(item, list):
def func(obj):
def func(*obj):
if item.window_id and self.id2item.get(item.window_id):
self.id2item[item.window_id]._present()
else:
def func(obj):
def func(*obj):
pass
return func
def generate_id(self, item):
return str(item.window_id)
return 'wm/' + str(item.window_id)
def display_menu_list(self, data, action_data, mlist):
menuitem = ('<item>\n'
'<attribute name="action">win.%s</attribute>\n'
'<attribute name="label" translatable="yes">'
'%s...</attribute>\n'
'</item>\n')
if isinstance(mlist, (list, tuple)):
i = mlist[0]
idval = self.generate_id(i)
data.write('<menu action="M:%s">' % idval)
action_data.append(("M:"+idval, None, i.submenu_label,
None, None, None))
data.write('<submenu>\n<attribute name="label"'
' translatable="yes">%s</attribute>\n' %
html.escape(i.submenu_label))
else:
i = mlist
idval = self.generate_id(i)
data.write('<menuitem action="%s"/>' % idval)
action_data.append((idval, None, i.menu_label, None, None,
self.call_back_factory(i)))
data.write(menuitem % (idval, html.escape(i.menu_label)))
action_data.append((idval, self.call_back_factory(i)))
if isinstance(mlist, (list, tuple)) and (len(mlist) > 1):
for i in mlist[1:]:
@@ -302,20 +307,17 @@ class GrampsWindowManager:
self.display_menu_list(data, action_data, i)
else:
idval = self.generate_id(i)
data.write('<menuitem action="%s"/>'
% self.generate_id(i))
action_data.append((idval, None, i.menu_label,
None, None,
self.call_back_factory(i)))
data.write(menuitem % (idval, html.escape(i.menu_label)))
action_data.append((idval, self.call_back_factory(i)))
if isinstance(mlist, (list, tuple)):
data.write('</menu>')
data.write('</submenu>\n')
def build_windows_menu(self):
if self.active != DISABLED:
self.uimanager.remove_ui(self.active)
self.uimanager.remove_action_group(self.action_group)
self.action_group = Gtk.ActionGroup(name='WindowManger')
self.action_group = ActionGroup(name='WindowManger')
action_data = []
data = StringIO()
+49 -31
View File
@@ -38,22 +38,21 @@ from gi.repository import Gdk
#-------------------------------------------------------------------------
from gramps.gen.plug import (START, END)
from .pluginmanager import GuiPluginManager
from .actiongroup import ActionGroup
from .uimanager import ActionGroup
#-------------------------------------------------------------------------
#
# Constants
#
#-------------------------------------------------------------------------
UICATEGORY = '''<ui>
<menubar name="MenuBar">
<menu action="ViewMenu">
<placeholder name="ViewsInCategory">%s
</placeholder>
</menu>
</menubar>
</ui>
'''
UICATEGORY = ''' <section id="ViewsInCatagory">
%s
</section>
'''
UICATAGORYBAR = ''' <placeholder id='ViewsInCategoryBar'>
%s
</placeholder>
'''
CATEGORY_ICON = {
'Dashboard': 'gramps-gramplet',
@@ -88,7 +87,6 @@ class Navigator:
self.active_view = None
self.ui_category = {}
self.view_toggle_actions = {}
self.cat_view_group = None
self.merge_ids = []
@@ -139,13 +137,35 @@ class Navigator:
"""
Load the sidebar plugins.
"""
menuitem = '''
<item>
<attribute name="action">win.ViewInCatagory</attribute>
<attribute name="label" translatable="yes">%s</attribute>
<attribute name="target">%d %d</attribute>
</item>
'''
baritem = '''
<child>
<object class="GtkToggleToolButton" id="bar%d">
<property name="action-name">win.ViewInCatagory</property>
<property name="action-target">'%d %d'</property>
<property name="icon-name">%s</property>
<property name="tooltip_text" translatable="yes">%s</property>
<property name="label" translatable="yes">%s</property>
</object>
<packing>
<property name="homogeneous">False</property>
</packing>
</child>
'''
plugman = GuiPluginManager.get_instance()
categories = []
views = {}
for cat_num, cat_views in enumerate(self.viewmanager.get_views()):
uimenuitems = ''
self.view_toggle_actions[cat_num] = []
uibaritems = ''
for view_num, page in enumerate(cat_views):
if view_num == 0:
@@ -156,26 +176,26 @@ class Navigator:
cat_icon = 'gramps-view'
categories.append([cat_num, cat_name, cat_icon])
pageid = 'page_%i_%i' % (cat_num, view_num)
uimenuitems += '\n<menuitem action="%s"/>' % pageid
# id, stock, button text, UI, tooltip, page
if view_num < 9:
modifier = "<PRIMARY><ALT>%d" % ((view_num % 9) + 1)
else:
modifier = ""
accel = "<PRIMARY><ALT>%d" % ((view_num % 9) + 1)
self.viewmanager.uimanager.app.set_accels_for_action(
"win.ViewInCatagory('%d %d')" % (cat_num, view_num),
[accel])
uimenuitems += menuitem % (page[0].name, cat_num, view_num)
stock_icon = page[0].stock_icon
if stock_icon is None:
stock_icon = cat_icon
self.view_toggle_actions[cat_num].append((pageid,
stock_icon,
page[0].name, modifier, page[0].name, view_num))
uibaritems += baritem % (view_num, cat_num, view_num,
stock_icon, page[0].name,
page[0].name)
views[cat_num].append((view_num, page[0].name, stock_icon))
if len(cat_views) > 1:
#allow for switching views in a category
self.ui_category[cat_num] = UICATEGORY % uimenuitems
self.ui_category[cat_num] = [UICATEGORY % uimenuitems,
UICATAGORYBAR % uibaritems]
for pdata in plugman.get_reg_sidebars():
module = plugman.load_plugin(pdata)
@@ -229,12 +249,10 @@ class Navigator:
list(map(uimanager.remove_ui, self.merge_ids))
if cat_num in self.ui_category:
self.cat_view_group = ActionGroup(name='viewmenu')
self.cat_view_group.add_radio_actions(
self.view_toggle_actions[cat_num], value=view_num,
on_change=self.cb_view_clicked, user_data=cat_num)
self.cat_view_group.set_sensitive(True)
uimanager.insert_action_group(self.cat_view_group, 1)
action = ('ViewInCatagory', self.cb_view_clicked, '',
str(cat_num) + ' ' + str(view_num))
self.cat_view_group = ActionGroup('viewmenu', [action])
uimanager.insert_action_group(self.cat_view_group)
mergeid = uimanager.add_ui_from_string(self.ui_category[cat_num])
self.merge_ids.append(mergeid)
@@ -245,12 +263,12 @@ class Navigator:
return
sidebar.view_changed(cat_num, view_num)
def cb_view_clicked(self, radioaction, current, cat_num):
def cb_view_clicked(self, radioaction, value):
"""
Called when a view is selected from the menu.
"""
view_num = radioaction.get_current_value()
self.viewmanager.goto_page(cat_num, view_num)
cat_num, view_num = value.get_string().split()
self.viewmanager.goto_page(int(cat_num), int(view_num))
def __menu_button_pressed(self, button, event):
"""
+31 -28
View File
@@ -63,6 +63,12 @@ from gramps.gen.plug import (CATEGORY_QR_PERSON, CATEGORY_QR_FAMILY, CATEGORY_QR
from ._textbufdoc import TextBufDoc
from gramps.gen.simple import make_basic_stylesheet
MENUITEM = ('<item>\n'
'<attribute name="action">{prefix}.{action}</attribute>\n'
'<attribute name="label" translatable="yes">'
'{label}</attribute>\n'
'</item>\n')
def flatten(L):
"""
Flattens a possibly nested list. Removes None results, too.
@@ -77,7 +83,7 @@ def flatten(L):
retval.append(L)
return retval
def create_web_connect_menu(dbstate, uistate, nav_group, handle):
def create_web_connect_menu(dbstate, uistate, nav_group, handle, prefix):
"""
This functions querries the registered web connects. It collects
the connects of the requested category, which must be one of
@@ -88,12 +94,12 @@ def create_web_connect_menu(dbstate, uistate, nav_group, handle):
handle as input method. A tuple is returned, containing the ui
string of the menu, and its associated actions.
"""
top = ("<placeholder id='WebConnect'><submenu>\n"
'<attribute name="label" translatable="yes">'
'Web Connection</attribute>\n')
actions = []
ofile = StringIO()
ofile.write('<menu action="WebConnect">')
actions.append(('WebConnect', None, _("Web Connect"), None, None, None))
menu = Gtk.Menu()
menu.show()
ofile.write(top)
#select the web connects to show
showlst = []
pmgr = GuiPluginManager.get_instance()
@@ -109,16 +115,17 @@ def create_web_connect_menu(dbstate, uistate, nav_group, handle):
connections.sort(key=lambda plug: plug.name)
actions = []
for connect in connections:
ofile.write('<menuitem action="%s"/>' % connect.key)
actions.append((connect.key, None, connect.name, None, None,
connect(dbstate, uistate, nav_group, handle)))
ofile.write('</menu>')
retval = [ofile.getvalue()]
retval.extend(actions)
return retval
action = connect.key.replace(' ', '-')
ofile.write(MENUITEM.format(prefix=prefix, action=action,
label=connect.name))
callback = connect(dbstate, uistate, nav_group, handle)
actions.append((action,
lambda x, y: callback(x)))
ofile.write('</submenu></placeholder>\n')
return (ofile.getvalue(), actions)
def create_quickreport_menu(category, dbstate, uistate, handle, track=[]):
def create_quickreport_menu(category, dbstate, uistate, handle, prefix, track=[]):
""" This functions querries the registered quick reports with
quick_report_list of _PluginMgr.py
It collects the reports of the requested category, which must be one of
@@ -132,39 +139,35 @@ def create_quickreport_menu(category, dbstate, uistate, handle, track=[]):
A tuple is returned, containing the ui string of the quick report menu,
and its associated actions
"""
top = ("<submenu>\n"
'<attribute name="label" translatable="yes">'
'Quick View</attribute>\n')
actions = []
ofile = StringIO()
ofile.write('<menu action="QuickReport">')
actions.append(('QuickReport', None, _("Quick View"), None, None, None))
menu = Gtk.Menu()
menu.show()
ofile.write(top)
#select the reports to show
showlst = []
pmgr = GuiPluginManager.get_instance()
for pdata in pmgr.get_reg_quick_reports():
if pdata.supported and pdata.category == category :
#add tuple function, translated name, name, status
showlst.append(pdata)
showlst.sort(key=lambda x: x.name)
for pdata in showlst:
new_key = pdata.id.replace(' ', '-')
ofile.write('<menuitem action="%s"/>' % new_key)
actions.append((new_key, None, pdata.name, None, None,
make_quick_report_callback(pdata, category, dbstate,
uistate, handle, track=track)))
ofile.write('</menu>')
ofile.write(MENUITEM.format(prefix=prefix, action=new_key,
label=pdata.name))
actions.append((new_key, make_quick_report_callback(
pdata, category, dbstate, uistate, handle, track=track)))
ofile.write('</submenu>\n')
return (ofile.getvalue(), actions)
def make_quick_report_callback(pdata, category, dbstate, uistate, handle,
track=[]):
return lambda x: run_report(dbstate, uistate, category, handle, pdata,
track=track)
return lambda x, y: run_report(dbstate, uistate, category, handle, pdata,
track=track)
def get_quick_report_list(qv_category=None):
"""
+517
View File
@@ -0,0 +1,517 @@
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2018 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.
#
"""
A replacement UIManager and ActionGroup.
"""
import copy
import sys
from ..gen.config import config
import logging
import xml.etree.ElementTree as ET
from gi.repository import GLib, Gio, Gtk
from ..gen.config import config
LOG = logging.getLogger('gui.uimanager')
ACTION_NAME = 0 # tuple index for action name
ACTION_CB = 1 # tuple index for action callback
ACTION_ACC = 2 # tuple index for action accelerator
ACTION_ST = 3 # tuple index for action state
class ActionGroup():
""" This class represents a group of actions that con be manipulated
together.
"""
def __init__(self, name, actionlist=None, prefix='win'):
"""
@param name: the action group name, used to match to the 'groups'
attribute in the ui xml.
@type name: string
@type actionlist: list
@param actionlist: the list of actions to add
The list contains tuples with the following contents:
string: Action Name
method: signal callback function.
None if just adding an accelerator
string: accelerator ex: '<Primary>Enter' or '' for no accelerator.
optional for non-stateful actions.
state: initial state for stateful actions.
'True' or 'False': the action is interpreted as a checkbox.
'None': non stateful action (optional)
'string': the action is interpreted as a Radio button
@type prefix: str
@param prefix: the prefix used by this group. If not provided, 'win'
is assumed.
"""
self.name = name
self.actionlist = actionlist if actionlist else []
self.prefix = prefix + '.'
self.act_group = None
self.sensitive = True
def add_actions(self, actionlist):
""" Add a list of actions to the current list
@type actionlist: list
@param actionlist: the list of actions to add
"""
self.actionlist.extend(actionlist)
class UIManager():
"""
This is Gramps UIManager, it is designed to replace the deprecated Gtk
UIManager. The replacement is not exact, but performs similar
functions, in some case with the same method names and parameters.
It is designed to be a singleton. The menu portion of this is responsible
only for Gramps main window menus and toolbar.
This was implemented to extend Gtk.Builder functions to allow editing
(merging) of the original builder XML with additional XML fragments during
operations. This allows changing of the menus and toolbar when the tree is
loaded, views are changed etc.
The ActionGroup portions can also be used by other windows. Other windows
needing menus or toolbars can create them via Gtk.Builder.
"""
def __init__(self, app, initial_xml):
"""
@param app: Gramps Gtk.Application reference
@type app: Gtk.Application
@param initial_xml: Initial (primary) XML string for Gramps menus and
toolbar
@type changexml: string
The xml is basically Gtk Builder xml, in particular the various menu
and toolbar elements. It is possible to add other elements as well.
The xml this supports has been extended in two ways;
1) there is an added "groups=" attribute to elements. This
attribute associates the element with one or more named ActionGroups
for making the element visible or not. If 'groups' is missing, the
element will be shown as long as enclosing elements are shown. The
element will be shown if the group is present and considered visible
by the uimanager. If more than one group is needed, they should be
separated by a space.
2) there is an added <placeholder> tag supported; this is used to mark
a place where merged UI XML can be inserted. During the update_menu
processing, elements enclosed in this tag pair are promoted to the
level of the placeholder tag, and the placeholder tag is removed.
Note that any elements can be merged (replaced) by the
add_ui_from_string method, not just placeholders. This works by
matching the "id=" attribute on the element, and replacing the
original element with the one from the add method.
Note that when added elements are removed by the remove_ui method, they
are replaced by the containing xml (with the 'id=') only, so don't put
anything inside of the containing xml to start with as it will be lost
during editing.
"""
self.app = app # the Gtk.Application of Gramps
self.et_xml = ET.fromstring(initial_xml)
self.builder = None
self.toolbar = None
self.action_groups = [] # current list of action groups
self.show_groups = [] # groups to show at the moment
self.accel_dict = {} # used to store accel overrides from file
def update_menu(self, init=False):
""" This updates the menus and toolbar when there is a change in the
ui; any addition or removal or set_visible operation needs to call
this. It is best to make the call only once, at the end, if multiple
changes are to be made.
It starts with the ET xml stored in self, cleans it up to meet the
Gtk.Builder specifications, and then updates the ui.
@param init: When True, this is first call and we set the builder
toolbar and menu to the application.
When False, we update the menus and toolbar
@type init: bool
"""
def iterator(parents):
""" This recursively goes through the ET xml and deals with the
'groups' attribute and <placeholder> tags, which are not valid for
builder. Empty submenus are also removed.
Groups processing removes elements that are not shown, as well as
the 'groups' attribute itself.
<placeholder> tags are removed and their enclosed elements are
promoted to the level of the placeholder.
@param parents: the current element to recursively process
@type parents: ET element
"""
indx = 0
while indx < len(parents):
child = parents[indx]
if len(child) >= 1:
# Recurse until we have a stand-alone child
iterator(child)
if((len(child) == 1 and child.tag == "submenu") or
(len(child) == 0 and child.tag == "section")):
# remove empty submenus and sections
# print('del', child.tag, child.attrib)
del parents[indx]
continue
# print(child.attrib)
groups = child.get('groups')
if not groups:
indx += 1
continue
del child.attrib['groups']
for group in groups.split(' '):
if group in self.show_groups:
indx += 1
break
else:
#print('del', child.tag, child.attrib, parents.tag,
# parents.attrib)
del parents[indx]
break
# The following looks for 'placeholder' elements and if found,
# promotes any children to the same level as the placeholder.
# this allows the user to insert elements without using a section.
indx = 0
while indx < len(parents):
if parents[indx].tag == "placeholder":
subtree = parents[indx]
#print('placholder del', parents[indx].tag,
# parents[indx].attrib, parents.tag, parents.attrib)
del parents[indx]
for child in subtree:
parents.insert(indx, child)
indx += 1
else:
indx += 1
if self.builder:
toolbar = self.builder.get_object("ToolBar") # previous toolbar
# need to copy the tree so we can preserve original for later edits.
editable = copy.deepcopy(self.et_xml)
iterator(editable) # clean up tree to builder specifications
xml_str = ET.tostring(editable, encoding="unicode")
#print(xml_str)
self.builder = Gtk.Builder.new_from_string(xml_str, -1)
if init:
self.app.menubar = self.builder.get_object("menubar")
self.app.set_menubar(self.app.menubar)
return
# The following is the only way I have found to update the menus.
# app.set_menubar can apparently only be used once, before
# ApplicationWindow creation, further uses do NOT cause the menus to
# update.
self.app.menubar.remove_all()
section = self.builder.get_object('menubar-update')
self.app.menubar.append_section(None, section)
# the following updates the toolbar from the new builder
toolbar_parent = toolbar.get_parent()
tb_show = toolbar.get_visible()
toolbar_parent.remove(toolbar)
toolbar = self.builder.get_object("ToolBar") # new toolbar
if config.get('interface.toolbar-text'):
toolbar.set_style(Gtk.ToolbarStyle.BOTH)
toolbar_parent.pack_start(toolbar, False, True, 0)
if tb_show:
toolbar.show_all()
else:
toolbar.hide()
#print('*** Update ui')
def add_ui_from_string(self, changexml):
""" This performs a merge operation on the xml elements that have
matching 'id's between the current ui xml and change xml strings.
The 'changexml' is a list of xml fragment strings used to replace
matching elements in the current xml.
There MUST one and only one matching id in the orig xml.
@param changexml: list of xml fragments to merge into main
@type changexml: list
@return: changexml
"""
try:
for xml in changexml:
if not xml:
# allow an xml fragment to be an empty string
continue
update = ET.fromstring(xml)
el_id = update.attrib['id']
# find the parent of the id'd element in original xml
parent = self.et_xml.find(".//*[@id='%s'].." % el_id)
if parent:
# we found it, now delete original, inset updated
for indx in range(len(parent)):
if parent[indx].get('id') == el_id:
del parent[indx]
parent.insert(indx, update)
else:
# updated item not present in original, just add it
# This allow addition of popups etc.
self.et_xml.append(update)
#results = ET.tostring(self.et_xml, encoding="unicode")
#print(results)
#print ('*** Add ui')
return changexml
except:
# the following is only here to assist debug
print('*****', sys.exc_info())
print(xml)
print(changexml)
assert False
def remove_ui(self, change_xml):
""" This removes the 'change_xml' from the current ui xml. It works on
any element with matching 'id', the actual element remains but any
children are removed.
The 'change_xml' is a list of xml strings originally used to replace
matching elements in the current ui xml.
@param change_xml: list of xml fragments to remove from main
@type change_xml: list
"""
# if not change_xml:
# import pydevd
# pydevd.settrace()
for xml in change_xml:
if not xml:
continue
update = ET.fromstring(xml)
el_id = update.attrib['id']
# find parent of id'd element
element = self.et_xml.find(".//*[@id='%s']" % el_id)
if element: # element may have already been deleted
for dummy in range(len(element)):
del element[0]
#results = ET.tostring(self.et_xml, encoding="unicode")
#print(results)
#print ('*** Remove ui')
return
def get_widget(self, obj):
""" Get the object from the builder.
@param obj: the widget to get
@type obj: string
@return: the object
"""
return self.builder.get_object(obj)
def insert_action_group(self, group, gio_group=None):
"""
This inserts (actually overwrites any matching actions) the action
group's actions to the app.
By default (with no gio_group), the action group is added to the main
Gramps window and the group assumes a 'win' prefix.
If not using the main window, the window MUST have the 'application'
property set for the accels to work. In this case the actiongroups
must be created like the following:
# create Gramps ActionGroup
self.action_group = ActionGroup('name', actions, 'prefix')
# create Gio action group
act_grp = SimpleActionGroup()
# associate window with Gio group and its prefix
window.insert_action_group('prefix', act_grp)
# make the window 'application' aware
window.set_application(uimanager.app)
# tell the uimanager about the groups.
uimanager.insert_action_group(self.action_group, act_grp)
@param group: the action group
@type group: ActionGroup
@param gio_group: the Gio action group associated with a window.
@type gio_group: Gio.SimpleActionGroup
"""
try:
assert isinstance(group.actionlist, list)
if gio_group:
window_group = group.act_group = gio_group
elif group.act_group:
window_group = group.act_group
else:
window_group = group.act_group = self.app.window
for item in group.actionlist:
# deal with accelerator overrides from a file
accel = self.accel_dict.get(group.prefix + item[ACTION_NAME])
if accel:
self.app.set_accels_for_action(
group.prefix + item[ACTION_NAME], [accel])
elif len(item) > 2 and item[ACTION_ACC]:
# deal with accelerators defined in the group
accels = self.app.get_actions_for_accel(item[ACTION_ACC])
if accels:
# diagnostic printout; a duplicate accelerator may be
# a problem if both are valid for the same window at
# the same time. If the actions are for a different
# window, this is not an error. Here we assume a
# different prefix is used for different windows.
for accel in accels:
if group.prefix in accel:
LOG.warning('**Duplicate Accelerator %s',
item[ACTION_ACC])
self.app.set_accels_for_action(
group.prefix + item[ACTION_NAME], [item[ACTION_ACC]])
if len(item) <= 3:
# Normal stateless actions
action = Gio.SimpleAction.new(item[ACTION_NAME], None)
if item[ACTION_CB]: # in case we have only accelerator
action.connect("activate", item[ACTION_CB])
elif isinstance(item[ACTION_ST], str):
# Radio Actions
action = Gio.SimpleAction.new_stateful(
item[ACTION_NAME], GLib.VariantType.new("s"),
GLib.Variant("s", item[ACTION_ST]))
action.connect("change-state", item[ACTION_CB])
elif isinstance(item[ACTION_ST], bool):
# Checkbox actions
action = Gio.SimpleAction.new_stateful(
item[ACTION_NAME], None,
GLib.Variant.new_boolean(item[ACTION_ST]))
action.connect("change-state", item[ACTION_CB])
window_group.add_action(action)
self.action_groups.append(group)
# if action sensitivity was set prior to actually inserting into
# UIManager, we need to do it now that we have the action
if not group.sensitive:
self.set_actions_sensitive(group, False)
except:
# the following is only to assist in debug
print(group.name, item)
assert False
def remove_action_group(self, group):
""" This removes the ActionGroup from the UIManager
@param group: the action group
@type group: ActionGroup
"""
if group.act_group:
window_group = group.act_group
else:
window_group = self.app.window
for item in group.actionlist:
window_group.remove_action(item[ACTION_NAME])
self.app.set_accels_for_action(group.prefix + item[ACTION_NAME],
[])
self.action_groups.remove(group)
def get_action_groups(self):
""" This returns a list of action Groups installed into the UIManager.
@return: list of groups
"""
return self.action_groups
def set_actions_sensitive(self, group, value):
""" This sets an ActionGroup enabled or disabled. A disabled action
will be greyed out in the UI.
@param group: the action group
@type group: ActionGroup
@param value: the state of the group
@type value: bool
"""
if group.act_group:
for item in group.actionlist:
action = group.act_group.lookup_action(item[ACTION_NAME])
if action:
# We check in case the group has not been inserted into
# UIManager yet
action.set_enabled(value)
group.sensitive = value
def get_actions_sensitive(self, group):
""" This gets an ActionGroup sensitive setting. A disabled action
will be greyed out in the UI.
We assume that the first action represents the group.
@param group: the action group
@type group: ActionGroup
@return: the state of the group
"""
item = group.actionlist[0]
action = group.act_group.lookup_action(item[ACTION_NAME])
return action.get_enabled()
def set_actions_visible(self, group, value):
""" This sets an ActionGroup visible and enabled or invisible and
disabled. Make sure that the menuitems or sections and toolbar items
have the 'groups=' xml attribute matching the group name for this to
work correctly.
@param group: the action group
@type group: ActionGroup
@param value: the state of the group
@type value: bool
"""
self.set_actions_sensitive(group, value)
if value:
if group.name not in self.show_groups:
self.show_groups.append(group.name)
else:
if group.name in self.show_groups:
self.show_groups.remove(group.name)
def get_action(self, group, actionname):
""" Return a single action from the group.
@param group: the action group
@type group: ActionGroup
@param actionname: the action name
@type actionname: string
@return: Gio.Action
"""
return group.act_group.lookup_action(actionname)
def dump_all_accels(self):
''' A function used diagnostically to see what accels are present.
This will only dump the current accel set, if other non-open windows
or views have accels, you will need to open them and run this again
and manually merge the result files. The results are in a
'gramps.accel' file located in the current working directory.'''
out_dict = {}
for group in self.action_groups:
for item in group.actionlist:
act = group.prefix + item[ACTION_NAME]
accels = self.app.get_accels_for_action(
group.prefix + item[ACTION_NAME])
out_dict[act] = accels[0] if accels else ''
import json
with open('gramps.accel', 'w', ) as hndl:
accels = json.dumps(out_dict, indent=0).replace('\n"', '\n# "')
hndl.write(accels)
def load_accels(self, filename):
""" This function loads accels from a file such as created by
dump_all_accels. The file contents is basically a Python dict
definition. As such it contains a line for each dict element.
These elements can be commented out with '#' at the beginning of the
line.
If used, this file overrides the accels defined in other Gramps code.
As such it must be loaded before any insert_action_group calls.
"""
import ast
with open(filename, 'r') as hndl:
accels = hndl.read()
self.accel_dict = ast.literal_eval(accels)
+281 -550
View File
File diff suppressed because it is too large Load Diff
+32 -19
View File
@@ -28,6 +28,7 @@
#-------------------------------------------------------------------------
from abc import ABCMeta, abstractmethod
from io import StringIO
import html
#-------------------------------------------------------------------------
#
@@ -51,9 +52,11 @@ from gi.repository import Gtk
from ..display import display_help
from ..listmodel import ListModel
from ..managedwindow import ManagedWindow
from ..uimanager import ActionGroup
from gramps.gen.utils.db import navigation_label
from gramps.gen.const import URL_MANUAL_PAGE
from gramps.gen.const import GRAMPS_LOCALE as glocale
from gramps.gen.errors import HandleError
_ = glocale.translation.sgettext
#-------------------------------------------------------------------------
@@ -71,9 +74,6 @@ WIKI_HELP_SEC = _('manual|Bookmarks')
#
#-------------------------------------------------------------------------
TOP = '''<ui><menubar name="MenuBar"><menu action="BookMenu">'''
BTM = '''</menu></menubar></ui>'''
DISABLED = -1
class Bookmarks(metaclass=ABCMeta):
@@ -93,7 +93,7 @@ class Bookmarks(metaclass=ABCMeta):
if self.dbstate.is_open():
self.update_bookmarks()
self.active = DISABLED
self.action_group = Gtk.ActionGroup(name='Bookmarks')
self.action_group = ActionGroup(name='Bookmarks')
if self.dbstate.is_open():
self.connect_signals()
self.dbstate.connect('database-changed', self.db_changed)
@@ -129,7 +129,8 @@ class Bookmarks(metaclass=ABCMeta):
"""
Redraw the display.
"""
self.redraw()
# used by navigationview; other updates follow
self.redraw(update_menu=False)
def undisplay(self):
"""
@@ -138,8 +139,7 @@ class Bookmarks(metaclass=ABCMeta):
if self.active != DISABLED:
self.uistate.uimanager.remove_ui(self.active)
self.uistate.uimanager.remove_action_group(self.action_group)
self.action_group = Gtk.ActionGroup(name='Bookmarks')
self.uistate.uimanager.ensure_update()
self.action_group = ActionGroup(name='Bookmarks')
self.active = DISABLED
def redraw_and_report_change(self):
@@ -147,36 +147,49 @@ class Bookmarks(metaclass=ABCMeta):
self.dbstate.db.report_bm_change()
self.redraw()
def redraw(self):
def redraw(self, update_menu=True):
"""Create the pulldown menu."""
menuitem = ('<item>\n'
'<attribute name="action">win.%s</attribute>\n'
'<attribute name="label" translatable="yes">'
'%s</attribute>\n'
'</item>\n')
text = StringIO()
text.write(TOP)
self.undisplay()
actions = []
count = 0
bad_bookmarks = [] # list of bad bookmarks
if self.dbstate.is_open() and len(self.bookmarks.get()) > 0:
text.write('<placeholder name="GoToBook">')
text.write('<section id="GoToBook">\n')
for item in self.bookmarks.get():
try:
label, dummy_obj = self.make_label(item)
func = self.callback(item)
action_id = "BM:%s" % item
actions.append((action_id, None, label, None, None, func))
text.write('<menuitem action="%s"/>' % action_id)
action_id = "BM.%s" % item
actions.append((action_id, func))
text.write(menuitem % (action_id, html.escape(label)))
count += 1
except AttributeError:
pass
text.write('</placeholder>')
except HandleError:
# if bookmark contains handle to something missing now
bad_bookmarks.append(item)
text.write('</section>\n')
text.write(BTM)
self.action_group.add_actions(actions)
self.uistate.uimanager.insert_action_group(self.action_group, 1)
self.active = self.uistate.uimanager.add_ui_from_string(text.getvalue())
self.uistate.uimanager.ensure_update()
self.uistate.uimanager.insert_action_group(self.action_group)
self.active = self.uistate.uimanager.add_ui_from_string(
[text.getvalue()])
if update_menu:
self.uistate.uimanager.update_menu()
text.close()
# Clean up any bad bookmarks (can happen if Gramps crashes;
# modified bookmarks set is saved only on normal Gramps close)
for handle in bad_bookmarks:
self.bookmarks.remove(handle)
@abstractmethod
def make_label(self, handle):
@@ -538,4 +551,4 @@ def make_callback(handle, function):
"""
Build a unique call to the function with the associated handle.
"""
return lambda x: function(handle)
return lambda x, y: function(handle)
+94 -82
View File
@@ -55,7 +55,7 @@ from gramps.gen.const import GRAMPS_LOCALE as glocale
_ = glocale.translation.sgettext
from .pageview import PageView
from .navigationview import NavigationView
from ..actiongroup import ActionGroup
from ..uimanager import ActionGroup
from ..columnorder import ColumnOrder
from gramps.gen.config import config
from gramps.gen.errors import WindowActiveError, FilterError, HandleError
@@ -64,6 +64,7 @@ from ..widgets.menuitem import add_menuitem
from gramps.gen.const import CUSTOM_FILTERS
from gramps.gen.utils.debug import profile
from gramps.gen.utils.string import data_recover_msg
from gramps.gen.plug import CATEGORY_QR_PERSON
from ..dialog import QuestionDialog, QuestionDialog2, ErrorDialog
from ..editors import FilterEditor
from ..ddtargets import DdTargets
@@ -122,6 +123,8 @@ class ListView(NavigationView):
self.generic_filter = None
dbstate.connect('database-changed', self.change_db)
self.connect_signals()
self.at_popup_action = None
self.at_popup_menu = None
def no_database(self):
## TODO GTK3: This is never called!! Dbguielement disconnects
@@ -206,26 +209,27 @@ class ListView(NavigationView):
NavigationView.define_actions(self)
self.edit_action = ActionGroup(name=self.title + '/ChangeOrder')
self.edit_action = ActionGroup(name=self.title + '/Edits')
self.edit_action.add_actions([
('Add', 'list-add', _("_Add..."), "<PRIMARY>Insert",
self.ADD_MSG, self.add),
('Remove', 'list-remove', _("_Delete"), "<PRIMARY>Delete",
self.DEL_MSG, self.remove),
('Merge', 'gramps-merge', _('_Merge...'), None,
self.MERGE_MSG, self.merge),
('ExportTab', None, _('Export View...'), None, None,
self.export),
])
('Add', self.add, '<Primary>Insert'),
('Remove', self.remove, '<Primary>Delete'),
('PRIMARY-BackSpace', self.remove, '<PRIMARY>BackSpace'),
('Merge', self.merge), ])
self._add_action_group(self.edit_action)
self.action_list.extend([
('ExportTab', self.export),
('Edit', self.edit, '<Primary>Return'),
('PRIMARY-J', self.jump, '<PRIMARY>J'),
('FilterEdit', self.filter_editor)])
self._add_action('Edit', 'gtk-edit', _("action|_Edit..."),
accel="<PRIMARY>Return",
tip=self.EDIT_MSG,
callback=self.edit)
def build_columns(self):
def build_columns(self, preserve_col=True):
"""
build the columns
"""
# Preserve the column widths if rebuilding the view.
if self.columns and preserve_col:
self.save_column_info()
list(map(self.list.remove_column, self.columns))
self.columns = []
@@ -291,7 +295,7 @@ class ListView(NavigationView):
Called when the page is displayed.
"""
NavigationView.set_active(self)
self.uistate.viewmanager.tags.tag_enable()
self.uistate.viewmanager.tags.tag_enable(update_menu=False)
self.uistate.show_filter_results(self.dbstate,
self.model.displayed(),
self.model.total())
@@ -303,10 +307,7 @@ class ListView(NavigationView):
NavigationView.set_inactive(self)
self.uistate.viewmanager.tags.tag_disable()
def __build_tree(self):
profile(self._build_tree)
def build_tree(self, force_sidebar=False):
def build_tree(self, force_sidebar=False, preserve_col=True):
if self.active:
cput0 = time.clock()
if not self.search_bar.is_visible():
@@ -335,7 +336,7 @@ class ListView(NavigationView):
parent=self.uistate.window)
cput1 = time.clock()
self.build_columns()
self.build_columns(preserve_col)
cput2 = time.clock()
self.list.set_model(self.model)
cput3 = time.clock()
@@ -372,7 +373,7 @@ class ListView(NavigationView):
"""
return 'gramps-tree-list'
def filter_editor(self, obj):
def filter_editor(self, *obj):
try:
FilterEditor(self.FILTER_TYPE , CUSTOM_FILTERS,
self.dbstate, self.uistate)
@@ -441,7 +442,7 @@ class ListView(NavigationView):
self.uistate.push_message(self.dbstate,
_("Active object not visible"))
def add_bookmark(self, obj):
def add_bookmark(self, *obj):
mlist = []
self.selection.selected_foreach(self.blist, mlist)
@@ -512,7 +513,7 @@ class ListView(NavigationView):
self.sort_col = 0
self.sort_order = Gtk.SortType.ASCENDING
self.setup_filter()
self.build_tree()
self.build_tree(preserve_col=False)
def column_order(self):
"""
@@ -851,6 +852,7 @@ class ListView(NavigationView):
"""
if not self.dbstate.is_open():
return False
menu = self.uimanager.get_widget('Popup')
if event.type == Gdk.EventType._2BUTTON_PRESS and event.button == 1:
if self.model.get_flags() & Gtk.TreeModelFlags.LIST_ONLY:
self.edit(obj)
@@ -866,49 +868,54 @@ class ListView(NavigationView):
else:
self.edit(obj)
return True
elif is_right_click(event):
menu = self.uistate.uimanager.get_widget('/Popup')
if menu:
# Quick Reports
qr_menu = self.uistate.uimanager.\
get_widget('/Popup/QuickReport')
if qr_menu and self.QR_CATEGORY > -1 :
(ui, qr_actions) = create_quickreport_menu(
self.QR_CATEGORY,
self.dbstate,
self.uistate,
self.first_selected())
self.__build_menu(qr_menu, qr_actions)
elif is_right_click(event) and menu:
prefix = 'win'
self.at_popup_menu = []
actions = []
# Quick Reports
if self.QR_CATEGORY > -1:
(qr_ui, qr_actions) = create_quickreport_menu(
self.QR_CATEGORY, self.dbstate, self.uistate,
self.first_selected(), prefix)
if self.get_active() and qr_actions:
actions.extend(qr_actions)
qr_ui = ("<placeholder id='QuickReport'>%s</placeholder>" %
qr_ui)
self.at_popup_menu.append(qr_ui)
# Web Connects
web_menu = self.uistate.uimanager.\
get_widget('/Popup/WebConnect')
if web_menu:
web_actions = create_web_connect_menu(
self.dbstate,
self.uistate,
self.navigation_type(),
self.first_selected())
self.__build_menu(web_menu, web_actions)
# Web Connects
if self.QR_CATEGORY == CATEGORY_QR_PERSON:
(web_ui, web_actions) = create_web_connect_menu(
self.dbstate, self.uistate, self.navigation_type(),
self.first_selected(), prefix)
if self.get_active() and web_actions:
actions.extend(web_actions)
self.at_popup_menu.append(web_ui)
menu.popup(None, None, None, None, event.button, event.time)
return True
if self.at_popup_action:
self.uimanager.remove_ui(self.at_popup_menu)
self.uimanager.remove_action_group(self.at_popup_action)
self.at_popup_action = ActionGroup('AtPopupActions',
actions)
self.uimanager.insert_action_group(self.at_popup_action)
self.at_popup_menu = self.uimanager.add_ui_from_string(
self.at_popup_menu)
self.uimanager.update_menu()
menu = self.uimanager.get_widget('Popup')
popup_menu = Gtk.Menu.new_from_model(menu)
popup_menu.attach_to_widget(obj, None)
popup_menu.show_all()
if Gtk.MINOR_VERSION < 22:
# ToDo The following is reported to work poorly with Wayland
popup_menu.popup(None, None, None, None,
event.button, event.time)
else:
popup_menu.popup_at_pointer(event)
return True
return False
def __build_menu(self, menu, actions):
"""
Build a submenu for quick reports and web connects
"""
if self.get_active() and len(actions) > 1:
sub_menu = Gtk.Menu()
for action in actions[1:]:
add_menuitem(sub_menu, action[2], None, action[5])
menu.set_submenu(sub_menu)
menu.show()
else:
menu.hide()
def _key_press(self, obj, event):
"""
Called when a key is pressed on a listview
@@ -1002,9 +1009,6 @@ class ListView(NavigationView):
return True
return False
def key_delete(self):
self.remove(None)
def change_page(self):
"""
Called when a page is changed.
@@ -1014,31 +1018,39 @@ class ListView(NavigationView):
self.uistate.show_filter_results(self.dbstate,
self.model.displayed(),
self.model.total())
self.edit_action.set_visible(True)
self.edit_action.set_sensitive(not self.dbstate.db.readonly)
self.uimanager.set_actions_visible(self.edit_action, True)
self.uimanager.set_actions_sensitive(self.edit_action,
not self.dbstate.db.readonly)
def on_delete(self):
"""
Save the column widths when the view is shutdown.
"""
self.save_column_info()
PageView.on_delete(self)
def save_column_info(self):
"""
Save the column widths, order, and view settings
"""
widths = self.get_column_widths()
order = self._config.get('columns.rank')
size = self._config.get('columns.size')
vis = self._config.get('columns.visible')
vis = self._config.get('columns.visible')
newsize = []
index = 0
for val, size in zip(order, size):
if val in vis:
size = widths[index]
if val in vis[:-1]: # don't use last column size, it's wrong
if widths[index]:
size = widths[index]
index += 1
newsize.append(size)
self._config.set('columns.size', newsize)
PageView.on_delete(self)
####################################################################
# Export data
####################################################################
def export(self, obj):
def export(self, *obj):
chooser = Gtk.FileChooserDialog(
_("Export View as Spreadsheet"),
self.uistate.window,
@@ -1140,25 +1152,25 @@ class ListView(NavigationView):
# Template functions
####################################################################
@abstractmethod
def edit(self, obj, data=None):
def edit(self, *obj):
"""
Template function to allow the editing of the selected object
"""
@abstractmethod
def remove(self, handle, data=None):
def remove(self, *obj):
"""
Template function to allow the removal of an object by its handle
"""
@abstractmethod
def add(self, obj, data=None):
def add(self, *obj):
"""
Template function to allow the adding of a new object
"""
@abstractmethod
def merge(self, obj, data=None):
def merge(self, *obj):
"""
Template function to allow the merger of two objects.
"""
@@ -1169,7 +1181,7 @@ class ListView(NavigationView):
Template function to allow the removal of an object by its handle
"""
def open_all_nodes(self, obj):
def open_all_nodes(self, *obj):
"""
Method for Treeviews to open all groups
obj: for use of method in event callback
@@ -1182,14 +1194,14 @@ class ListView(NavigationView):
self.uistate.set_busy_cursor(False)
self.uistate.modify_statusbar(self.dbstate)
def close_all_nodes(self, obj):
def close_all_nodes(self, *obj):
"""
Method for Treeviews to close all groups
obj: for use of method in event callback
"""
self.list.collapse_all()
def open_branch(self, obj):
def open_branch(self, *obj):
"""
Expand the selected branches and all children.
obj: for use of method in event callback
@@ -1204,7 +1216,7 @@ class ListView(NavigationView):
self.uistate.set_busy_cursor(False)
self.uistate.modify_statusbar(self.dbstate)
def close_branch(self, obj):
def close_branch(self, *obj):
"""
Collapse the selected branches.
:param obj: not used, present only to allow the use of the method in
+69 -74
View File
@@ -29,6 +29,7 @@ Provide the base classes for GRAMPS' DataView classes
#
#----------------------------------------------------------------
from abc import abstractmethod
import html
import logging
_LOG = logging.getLogger('.navigationview')
@@ -49,7 +50,7 @@ from gi.repository import Gtk
from gramps.gen.const import GRAMPS_LOCALE as glocale
_ = glocale.translation.sgettext
from .pageview import PageView
from ..actiongroup import ActionGroup
from ..uimanager import ActionGroup
from gramps.gen.utils.db import navigation_label
from gramps.gen.constfunc import mod_key
from ..utils import match_primary_mask
@@ -57,19 +58,9 @@ from ..utils import match_primary_mask
DISABLED = -1
MRU_SIZE = 10
MRU_TOP = [
'<ui>'
'<menubar name="MenuBar">'
'<menu action="GoMenu">'
'<placeholder name="CommonHistory">'
]
MRU_TOP = '<section id="CommonHistory">'
MRU_BTM = '</section>'
MRU_BTM = [
'</placeholder>'
'</menu>'
'</menubar>'
'</ui>'
]
#------------------------------------------------------------------------------
#
# NavigationView
@@ -94,6 +85,7 @@ class NavigationView(PageView):
self.mru_signal = None
self.nav_group = nav_group
self.mru_active = DISABLED
self.uimanager = uistate.uimanager
self.uistate.register(state, self.navigation_type(), self.nav_group)
@@ -122,8 +114,8 @@ class NavigationView(PageView):
"""
PageView.disable_action_group(self)
self.fwd_action.set_visible(False)
self.back_action.set_visible(False)
self.uimanager.set_actions_visible(self.fwd_action, False)
self.uimanager.set_actions_visible(self.back_action, False)
def enable_action_group(self, obj):
"""
@@ -133,20 +125,25 @@ class NavigationView(PageView):
"""
PageView.enable_action_group(self, obj)
self.fwd_action.set_visible(True)
self.back_action.set_visible(True)
self.uimanager.set_actions_visible(self.fwd_action, True)
self.uimanager.set_actions_visible(self.back_action, True)
hobj = self.get_history()
self.fwd_action.set_sensitive(not hobj.at_end())
self.back_action.set_sensitive(not hobj.at_front())
self.uimanager.set_actions_sensitive(self.fwd_action,
not hobj.at_end())
self.uimanager.set_actions_sensitive(self.back_action,
not hobj.at_front())
def change_page(self):
"""
Called when the page changes.
"""
hobj = self.get_history()
self.fwd_action.set_sensitive(not hobj.at_end())
self.back_action.set_sensitive(not hobj.at_front())
self.other_action.set_sensitive(not self.dbstate.db.readonly)
self.uimanager.set_actions_sensitive(self.fwd_action,
not hobj.at_end())
self.uimanager.set_actions_sensitive(self.back_action,
not hobj.at_front())
self.uimanager.set_actions_sensitive(self.other_action,
not self.dbstate.db.readonly)
self.uistate.modify_statusbar(self.dbstate)
def set_active(self):
@@ -159,7 +156,7 @@ class NavigationView(PageView):
hobj = self.get_history()
self.active_signal = hobj.connect('active-changed', self.goto_active)
self.mru_signal = hobj.connect('mru-changed', self.update_mru_menu)
self.update_mru_menu(hobj.mru)
self.update_mru_menu(hobj.mru, update_menu=False)
self.goto_active(None)
@@ -199,8 +196,10 @@ class NavigationView(PageView):
self.goto_handle(active_handle)
hobj = self.get_history()
self.fwd_action.set_sensitive(not hobj.at_end())
self.back_action.set_sensitive(not hobj.at_front())
self.uimanager.set_actions_sensitive(self.fwd_action,
not hobj.at_end())
self.uimanager.set_actions_sensitive(self.back_action,
not hobj.at_front())
def get_active(self):
"""
@@ -238,7 +237,7 @@ class NavigationView(PageView):
####################################################################
# BOOKMARKS
####################################################################
def add_bookmark(self, obj):
def add_bookmark(self, *obj):
"""
Add a bookmark to the list.
"""
@@ -259,7 +258,7 @@ class NavigationView(PageView):
"no one was selected."),
parent=self.uistate.window)
def edit_bookmarks(self, obj):
def edit_bookmarks(self, *obj):
"""
Call the bookmark editor.
"""
@@ -271,12 +270,8 @@ class NavigationView(PageView):
"""
self.book_action = ActionGroup(name=self.title + '/Bookmark')
self.book_action.add_actions([
('AddBook', 'gramps-bookmark-new', _('_Add Bookmark'),
'<PRIMARY>d', None, self.add_bookmark),
('EditBook', 'gramps-bookmark-edit',
_("%(title)s...") % {'title': _("Organize Bookmarks")},
'<shift><PRIMARY>D', None,
self.edit_bookmarks),
('AddBook', self.add_bookmark, '<PRIMARY>d'),
('EditBook', self.edit_bookmarks, '<shift><PRIMARY>D'),
])
self._add_action_group(self.book_action)
@@ -290,35 +285,25 @@ class NavigationView(PageView):
"""
# add the Forward action group to handle the Forward button
self.fwd_action = ActionGroup(name=self.title + '/Forward')
self.fwd_action.add_actions([
('Forward', 'go-next', _("_Forward"),
"%sRight" % mod_key(), _("Go to the next object in the history"),
self.fwd_clicked)
])
self.fwd_action.add_actions([('Forward', self.fwd_clicked,
"%sRight" % mod_key())])
# add the Backward action group to handle the Forward button
self.back_action = ActionGroup(name=self.title + '/Backward')
self.back_action.add_actions([
('Back', 'go-previous', _("_Back"),
"%sLeft" % mod_key(), _("Go to the previous object in the history"),
self.back_clicked)
])
self.back_action.add_actions([('Back', self.back_clicked,
"%sLeft" % mod_key())])
self._add_action('HomePerson', 'go-home', _("_Home"),
accel="%sHome" % mod_key(),
tip=_("Go to the default person"), callback=self.home)
self._add_action('HomePerson', self.home, "%sHome" % mod_key())
self.other_action = ActionGroup(name=self.title + '/PersonOther')
self.other_action.add_actions([
('SetActive', 'go-home', _("Set _Home Person"), None,
None, self.set_default_person),
])
('SetActive', self.set_default_person)])
self._add_action_group(self.back_action)
self._add_action_group(self.fwd_action)
self._add_action_group(self.other_action)
def set_default_person(self, obj):
def set_default_person(self, *obj):
"""
Set the default person.
"""
@@ -326,7 +311,7 @@ class NavigationView(PageView):
if active:
self.dbstate.db.set_default_person_handle(active)
def home(self, obj):
def home(self, *obj):
"""
Move to the default person.
"""
@@ -342,7 +327,7 @@ class NavigationView(PageView):
"via the menu Edit ->Set Home Person."),
parent=self.uistate.window)
def jump(self):
def jump(self, *obj):
"""
A dialog to move to a Gramps ID entered by the user.
"""
@@ -383,7 +368,7 @@ class NavigationView(PageView):
"""
pass
def fwd_clicked(self, obj):
def fwd_clicked(self, *obj):
"""
Move forward one object in the history.
"""
@@ -392,11 +377,12 @@ class NavigationView(PageView):
if not hobj.at_end():
hobj.forward()
self.uistate.modify_statusbar(self.dbstate)
self.fwd_action.set_sensitive(not hobj.at_end())
self.back_action.set_sensitive(True)
self.uimanager.set_actions_sensitive(self.fwd_action,
not hobj.at_end())
self.uimanager.set_actions_sensitive(self.back_action, True)
hobj.lock = False
def back_clicked(self, obj):
def back_clicked(self, *obj):
"""
Move backward one object in the history.
"""
@@ -405,8 +391,9 @@ class NavigationView(PageView):
if not hobj.at_front():
hobj.back()
self.uistate.modify_statusbar(self.dbstate)
self.back_action.set_sensitive(not hobj.at_front())
self.fwd_action.set_sensitive(True)
self.uimanager.set_actions_sensitive(self.back_action,
not hobj.at_front())
self.uimanager.set_actions_sensitive(self.fwd_action, True)
hobj.lock = False
####################################################################
@@ -418,44 +405,52 @@ class NavigationView(PageView):
Remove the UI and action groups for the MRU list.
"""
if self.mru_active != DISABLED:
self.uistate.uimanager.remove_ui(self.mru_active)
self.uistate.uimanager.remove_action_group(self.mru_action)
self.uimanager.remove_ui(self.mru_active)
self.uimanager.remove_action_group(self.mru_action)
self.mru_active = DISABLED
def mru_enable(self):
def mru_enable(self, update_menu=False):
"""
Enables the UI and action groups for the MRU list.
"""
if self.mru_active == DISABLED:
self.uistate.uimanager.insert_action_group(self.mru_action, 1)
self.mru_active = self.uistate.uimanager.add_ui_from_string(self.mru_ui)
self.uistate.uimanager.ensure_update()
self.uimanager.insert_action_group(self.mru_action)
self.mru_active = self.uimanager.add_ui_from_string(self.mru_ui)
if update_menu:
self.uimanager.update_menu()
def update_mru_menu(self, items):
def update_mru_menu(self, items, update_menu=True):
"""
Builds the UI and action group for the MRU list.
"""
menuitem = ''' <item>
<attribute name="action">win.%s%02d</attribute>
<attribute name="label" translatable="yes">%s</attribute>
</item>
'''
menus = ''
self.mru_disable()
nav_type = self.navigation_type()
hobj = self.get_history()
menu_len = min(len(items) - 1, MRU_SIZE)
entry = '<menuitem action="%s%02d"/>'
data = [entry % (nav_type, index) for index in range(0, menu_len)]
self.mru_ui = "".join(MRU_TOP) + "".join(data) + "".join(MRU_BTM)
for index in range(0, menu_len):
name, obj = navigation_label(self.dbstate.db, nav_type,
items[index])
menus += menuitem % (nav_type, index, html.escape(name))
self.mru_ui = [MRU_TOP + menus + MRU_BTM]
mitems = items[-MRU_SIZE - 1:-1] # Ignore current handle
mitems.reverse()
data = []
for index, handle in enumerate(mitems):
name, obj = navigation_label(self.dbstate.db, nav_type, handle)
data.append(('%s%02d'%(nav_type, index), None, name,
"%s%d" % (mod_key(), index), None,
make_callback(hobj.push, handle)))
data.append(('%s%02d'%(nav_type, index),
make_callback(hobj.push, handle),
"%s%d" % (mod_key(), index)))
self.mru_action = ActionGroup(name=self.title + '/MRU')
self.mru_action.add_actions(data)
self.mru_enable()
self.mru_enable(update_menu)
####################################################################
# Template functions
@@ -503,4 +498,4 @@ def make_callback(func, handle):
"""
Generates a callback function based off the passed arguments
"""
return lambda x: func(handle)
return lambda x, y: func(handle)
+33 -50
View File
@@ -51,7 +51,7 @@ from ..dbguielement import DbGUIElement
from ..widgets.grampletbar import GrampletBar
from ..configure import ConfigureDialog
from gramps.gen.config import config
from ..actiongroup import ActionGroup
from ..uimanager import ActionGroup
#------------------------------------------------------------------------------
#
@@ -97,25 +97,24 @@ class PageView(DbGUIElement, metaclass=ABCMeta):
self.uistate = uistate
self.action_list = []
self.action_toggle_list = []
self.action_toolmenu_list = []
self.action_toolmenu = {} #easy access to toolmenuaction and proxies
self.action_group = None
self.additional_action_groups = []
self.additional_uis = []
self.ui_def = '''<ui>
<menubar name="MenuBar">
<menu action="ViewMenu">
<placeholder name="Bars">
<menuitem action="Sidebar"/>
<menuitem action="Bottombar"/>
</placeholder>
</menu>
</menubar>
</ui>'''
self.ui_def = ['''
<placeholder id="Bars">
<item>
<attribute name="action">win.Sidebar</attribute>
<attribute name="label" translatable="yes">_Sidebar</attribute>
</item>
<item>
<attribute name="action">win.Bottombar</attribute>
<attribute name="label" translatable="yes">_Bottombar</attribute>
</item>
</placeholder>
''']
self.dirty = True
self.active = False
self._dirty_on_change_inactive = True
self.func_list = {}
if isinstance(self.pdata.category, tuple):
self.category, self.translated_category = self.pdata.category
@@ -201,24 +200,24 @@ class PageView(DbGUIElement, metaclass=ABCMeta):
"""
self._config.set(setting, widget.get_position())
def __sidebar_toggled(self, action):
def __sidebar_toggled(self, action, value):
"""
Called when the sidebar is toggled.
"""
active = action.get_active()
if active:
action.set_state(value) # change GUI
if value.get_boolean():
self.sidebar.show()
self.sidebar_toggled(True)
else:
self.sidebar.hide()
self.sidebar_toggled(False)
def __bottombar_toggled(self, action):
def __bottombar_toggled(self, action, value):
"""
Called when the bottombar is toggled.
"""
active = action.get_active()
if active:
action.set_state(value) # change GUI
if value.get_boolean():
self.bottombar.show()
else:
self.bottombar.hide()
@@ -277,7 +276,7 @@ class PageView(DbGUIElement, metaclass=ABCMeta):
def get_data(self):
return self.data
class Context:
targets = [drag_type]
targets = [drag_type.name()]
action = 1
def list_targets(self):
return Context.targets
@@ -312,12 +311,6 @@ class PageView(DbGUIElement, metaclass=ABCMeta):
return True
return False
def call_function(self, key):
"""
Calls the function associated with the key value
"""
self.func_list.get(key)()
def post(self):
"""
Called after a page is created.
@@ -332,6 +325,9 @@ class PageView(DbGUIElement, metaclass=ABCMeta):
self.sidebar.set_active()
self.bottombar.set_active()
self.active = True
new_title = "%s - %s - Gramps" % (self.dbstate.db.get_dbname(),
self.get_title())
self.uistate.window.set_title(new_title)
if self.dirty:
self.uistate.set_busy_cursor(True)
self.build_tree()
@@ -370,14 +366,15 @@ class PageView(DbGUIElement, metaclass=ABCMeta):
Turns off the visibility of the View's action group, if defined
"""
if self.action_group:
self.action_group.set_visible(False)
self.uistate.uimanager.set_actions_visible(self.action_group,
False)
def enable_action_group(self, obj):
"""
Turns on the visibility of the View's action group, if defined
"""
if self.action_group:
self.action_group.set_visible(True)
self.uistate.uimanager.set_actions_visible(self.action_group, True)
def get_stock(self):
"""
@@ -435,11 +432,9 @@ class PageView(DbGUIElement, metaclass=ABCMeta):
View. The user typically defines self.action_list and
self.action_toggle_list in this function.
"""
self._add_toggle_action('Sidebar', None, _('_Sidebar'),
"<shift><PRIMARY>R", None, self.__sidebar_toggled,
self._add_toggle_action('Sidebar', self.__sidebar_toggled, '',
self.sidebar.get_property('visible'))
self._add_toggle_action('Bottombar', None, _('_Bottombar'),
"<shift><PRIMARY>B", None, self.__bottombar_toggled,
self._add_toggle_action('Bottombar', self.__bottombar_toggled, '',
self.bottombar.get_property('visible'))
def __build_action_group(self):
@@ -452,31 +447,19 @@ class PageView(DbGUIElement, metaclass=ABCMeta):
if len(self.action_list) > 0:
self.action_group.add_actions(self.action_list)
if len(self.action_toggle_list) > 0:
self.action_group.add_toggle_actions(self.action_toggle_list)
self.action_group.add_actions(self.action_toggle_list)
def _add_action(self, name, icon_name, label, accel=None, tip=None,
callback=None):
def _add_action(self, name, callback=None, accel=None):
"""
Add an action to the action list for the current view.
"""
self.action_list.append((name, icon_name, label, accel, tip,
callback))
self.action_list.append((name, callback, accel))
def _add_toggle_action(self, name, icon_name, label, accel=None,
tip=None, callback=None, value=False):
def _add_toggle_action(self, name, callback=None, accel= None, value=False):
"""
Add a toggle action to the action list for the current view.
"""
self.action_toggle_list.append((name, icon_name, label, accel,
tip, callback, value))
def _add_toolmenu_action(self, name, label, tooltip, callback,
arrowtooltip):
"""
Add a menu action to the action list for the current view.
"""
self.action_toolmenu_list.append((name, label, tooltip, callback,
arrowtooltip))
self.action_toggle_list.append((name, callback, accel, value))
def get_actions(self):
"""
+80 -46
View File
@@ -50,7 +50,7 @@ from gramps.gen.const import URL_MANUAL_PAGE
from ..display import display_help
from ..dialog import ErrorDialog, QuestionDialog2
import gramps.gui.widgets.progressdialog as progressdlg
from ..actiongroup import ActionGroup
from ..uimanager import ActionGroup
from ..managedwindow import ManagedWindow
#-------------------------------------------------------------------------
@@ -58,29 +58,54 @@ from ..managedwindow import ManagedWindow
# Constants
#
#-------------------------------------------------------------------------
TAG_1 = '''<ui>
<menubar name="MenuBar">
<menu action="EditMenu">
<placeholder name="TagMenu">
<menu action="Tag">
'''
TAG_1 = '''
<section id='TagMenu' groups='RW'>
<submenu>
<attribute name="label" translatable="yes">Tag</attribute>
%s
</submenu>
</section>
'''
TAG_2 = '''
</menu>
</placeholder>
</menu>
</menubar>
<toolbar name="ToolBar">
<placeholder name="TagTool">
<toolitem action="TagButton"/>
TAG_2 = (
''' <placeholder id='TagTool' groups='RW'>
<child groups='RO'>
<object class="GtkToolButton" id="TagButton">
<property name="icon-name">gramps-tag</property>
<property name="action-name">win.TagButton</property>
<property name="tooltip_text" translatable="yes">'''
'''Tag selected rows</property>
<property name="label" translatable="yes">Tag</property>
</object>
<packing>
<property name="homogeneous">False</property>
</packing>
</child>
</placeholder>
</toolbar>
<popup name="TagPopup">
'''
''')
TAG_3 = '''
</popup>
</ui>'''
<menu id='TagPopup' groups='RW'>
%s
</menu>'''
TAG_MENU = (
'''<section>
<item>
<attribute name="action">win.NewTag</attribute>
<attribute name="label" translatable="yes">'''
'''New Tag...</attribute>
</item>
<item>
<attribute name="action">win.OrganizeTags</attribute>
<attribute name="label" translatable="yes">'''
'''Organize Tags...</attribute>
</item>
</section>
<section>
%s
</section>
''')
WIKI_HELP_PAGE = '%s_-_Filters' % \
URL_MANUAL_PAGE
@@ -119,13 +144,14 @@ class Tags(DbGUIElement):
self._build_tag_menu()
def tag_enable(self):
def tag_enable(self, update_menu=True):
"""
Enables the UI and action groups for the tag menu.
"""
self.uistate.uimanager.insert_action_group(self.tag_action, 1)
self.uistate.uimanager.insert_action_group(self.tag_action)
self.tag_id = self.uistate.uimanager.add_ui_from_string(self.tag_ui)
self.uistate.uimanager.ensure_update()
if update_menu:
self.uistate.uimanager.update_menu()
def tag_disable(self):
"""
@@ -134,7 +160,6 @@ class Tags(DbGUIElement):
if self.tag_id is not None:
self.uistate.uimanager.remove_ui(self.tag_id)
self.uistate.uimanager.remove_action_group(self.tag_action)
self.uistate.uimanager.ensure_update()
self.tag_id = None
def _db_changed(self, db):
@@ -209,46 +234,55 @@ class Tags(DbGUIElement):
actions = []
if not self.dbstate.is_open():
self.tag_ui = ''
self.tag_ui = ['']
self.tag_action = ActionGroup(name='Tag')
return
tag_menu = '<menuitem action="NewTag"/>'
tag_menu += '<menuitem action="OrganizeTags"/>'
tag_menu += '<separator/>'
tag_menu = ''
menuitem = '''
<item>
<attribute name="action">win.TAG_%s</attribute>
<attribute name="label" translatable="yes">%s</attribute>
</item>'''
for tag_name, handle in self.__tag_list:
tag_menu += '<menuitem action="TAG_%s"/>' % handle
actions.append(('TAG_%s' % handle, None, tag_name, None, None,
make_callback(self.tag_selected_rows, handle)))
tag_menu += menuitem % (handle, tag_name)
actions.append(('TAG_%s' % handle,
make_callback(self.tag_selected_rows, handle)))
tag_menu = TAG_MENU % tag_menu
self.tag_ui = TAG_1 + tag_menu + TAG_2 + tag_menu + TAG_3
self.tag_ui = [TAG_1 % tag_menu, TAG_2, TAG_3 % tag_menu]
actions.append(('Tag', 'gramps-tag', _('Tag'), None, None, None))
actions.append(('NewTag', 'gramps-tag-new', _('New Tag...'), None, None,
self.cb_new_tag))
actions.append(('OrganizeTags', None, _('Organize Tags...'), None, None,
self.cb_organize_tags))
actions.append(('TagButton', 'gramps-tag', _('Tag'), None,
_('Tag selected rows'), self.cb_tag_button))
actions.append(('NewTag', self.cb_new_tag))
actions.append(('OrganizeTags', self.cb_organize_tags))
actions.append(('TagButton', self.cb_tag_button))
self.tag_action = ActionGroup(name='Tag')
self.tag_action.add_actions(actions)
def cb_tag_button(self, action):
def cb_tag_button(self, *args):
"""
Display the popup menu when the toolbar button is clicked.
"""
menu = self.uistate.uimanager.get_widget('/TagPopup')
button = self.uistate.uimanager.get_widget('/ToolBar/TagTool/TagButton')
menu.popup(None, None, cb_menu_position, button, 0, 0)
menu = self.uistate.uimanager.get_widget('TagPopup')
button = self.uistate.uimanager.get_widget('TagButton')
popup_menu = Gtk.Menu.new_from_model(menu)
popup_menu.attach_to_widget(button, None)
popup_menu.show_all()
if Gtk.MINOR_VERSION < 22:
# ToDo The following is reported to work poorly with Wayland
popup_menu.popup(None, None, cb_menu_position, button, 0, 0)
else:
popup_menu.popup_at_widget(button, Gdk.Gravity.SOUTH,
Gdk.Gravity.NORTH_WEST, None)
def cb_organize_tags(self, action):
def cb_organize_tags(self, *action):
"""
Display the Organize Tags dialog.
"""
OrganizeTagsDialog(self.db, self.uistate, [])
def cb_new_tag(self, action):
def cb_new_tag(self, *action):
"""
Create a new tag and tag the selected objects.
"""
@@ -304,7 +338,7 @@ def make_callback(func, tag_handle):
"""
Generates a callback function based off the passed arguments
"""
return lambda x: func(tag_handle)
return lambda x, y: func(tag_handle)
#-------------------------------------------------------------------------
#
-4
View File
@@ -34,16 +34,12 @@ from .monitoredwidgets import *
from .selectionwidget import SelectionWidget, Region
from .shadebox import *
from .shortlistcomboentry import *
from .springseparator import *
from .statusbar import Statusbar
from .styledtextbuffer import *
from .styledtexteditor import *
from .toolcomboentry import *
from .undoablebuffer import *
from .undoableentry import *
from .undoablestyledbuffer import *
from .validatedcomboentry import *
from .validatedmaskedentry import *
from .valueaction import *
from .valuetoolitem import *
from .placewithin import *
+31 -1
View File
@@ -104,6 +104,16 @@ class GrampletBar(Gtk.Notebook):
self.set_show_border(False)
self.set_scrollable(True)
image = Gtk.Image(stock=Gtk.STOCK_REFRESH)
refresh_button = Gtk.Button(image=image)
refresh_button.set_relief(Gtk.ReliefStyle.NONE)
refresh_button.connect('clicked', self.__refresh_clicked)
self.set_action_widget(refresh_button, Gtk.PackType.START)
if config.get('interface.grampletbar-refresh'):
refresh_button.show()
else:
refresh_button.hide()
book_button = Gtk.Button()
# Arrow is too small unless in a box
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
@@ -142,6 +152,7 @@ class GrampletBar(Gtk.Notebook):
self.set_current_page(config_settings[1])
uistate.connect('grampletbar-close-changed', self.cb_close_changed)
uistate.connect('grampletbar-refresh-changed', self.cb_refresh_changed)
# Connect after gramplets added to prevent making them active
self.connect('switch-page', self.__switch_page)
@@ -404,6 +415,16 @@ class GrampletBar(Gtk.Notebook):
tablabel = self.get_tab_label(gramplet)
tablabel.use_close(config.get('interface.grampletbar-close'))
def cb_refresh_changed(self):
"""
Refresh button preference changed.
"""
button = self.get_action_widget(Gtk.PackType.START)
if config.get('interface.grampletbar-refresh'):
button.show()
else:
button.hide()
def __delete_clicked(self, button, gramplet):
"""
Called when the delete button is clicked.
@@ -476,6 +497,15 @@ class GrampletBar(Gtk.Notebook):
gramplet.detached_window.close()
gramplet.detached_window = None
def __refresh_clicked(self, button):
"""
Called when the drop-down button is clicked.
"""
for gramplet in self.get_children():
if gramplet and gramplet.pui:
if gramplet.pui.active:
gramplet.pui.main()
def __button_clicked(self, button):
"""
Called when the drop-down button is clicked.
@@ -743,7 +773,7 @@ class TabLabel(Gtk.Box):
def use_close(self, use_close):
"""
Display the cose button according to user preference.
Display the close button according to user preference.
"""
if use_close:
self.closebtn.show()
+86 -34
View File
@@ -34,6 +34,7 @@ from gi.repository import Pango
import time
import os
import configparser
from gramps.gen.config import config
import logging
@@ -49,7 +50,7 @@ from gramps.gen.const import URL_MANUAL_PAGE, VERSION_DIR, COLON
from ..editors import EditPerson, EditFamily
from ..managedwindow import ManagedWindow
from ..utils import is_right_click, match_primary_mask, get_link_color
from .menuitem import add_menuitem
from ..uimanager import ActionGroup
from ..plug import make_gui_option
from ..plug.quick import run_quick_report_by_name
from ..display import display_help, display_url
@@ -189,6 +190,13 @@ def logical_true(value):
"""
return value in ["True", True, 1, "1"]
def make_callback(func, arg):
"""
Generates a callback function based off the passed arguments
"""
return lambda x, y: func(arg)
class LinkTag(Gtk.TextTag):
"""
Class for keeping track of link data.
@@ -235,6 +243,8 @@ class GrampletWindow(ManagedWindow):
self.setup_configs('interface.' + cfg_name,
gramplet.detached_width, gramplet.detached_height)
self.window.add_button(_('_Help'), Gtk.ResponseType.HELP)
if config.get('interface.grampletbar-refresh'):
self.window.add_button(_('Refresh'), Gtk.ResponseType.APPLY)
# add gramplet:
if self.gramplet.pui:
self.gramplet.pui.active = True
@@ -267,6 +277,8 @@ class GrampletWindow(ManagedWindow):
else:
display_help(WIKI_HELP_PAGE,
self.gramplet.tname.replace(" ", "_"))
elif response == Gtk.ResponseType.APPLY:
self.refresh()
def build_menu_names(self, obj):
"""
@@ -274,6 +286,12 @@ class GrampletWindow(ManagedWindow):
"""
return (self.title, 'Gramplet')
def refresh(self):
"""
Refresh the detached gramplet
"""
self.gramplet.pui.main() # refresh
def get_title(self):
"""
Returns the window title.
@@ -999,6 +1017,8 @@ class GrampletPane(Gtk.ScrolledWindow):
self.pageview = pageview
self.pane = self
self._popup_xy = None
self.at_popup_action = None
self.at_popup_menu = None
user_gramplets = self.load_gramplets()
# build the GUI:
msg = _("Right click to add gramplets")
@@ -1349,8 +1369,7 @@ class GrampletPane(Gtk.ScrolledWindow):
self.place_gramplets(recolumn=True)
self.show()
def restore_gramplet(self, obj):
name = obj.get_child().get_label()
def restore_gramplet(self, name):
############### First kind: from current session
for gramplet in self.closed_gramplets:
if gramplet.title == name:
@@ -1392,8 +1411,7 @@ class GrampletPane(Gtk.ScrolledWindow):
else:
self.drop_widget(self, gramplet, 0, 0, 0)
def add_gramplet(self, obj):
tname = obj.get_child().get_label()
def add_gramplet(self, tname):
all_opts = get_gramplet_options_by_tname(tname)
name = all_opts["name"]
if all_opts is None:
@@ -1437,39 +1455,73 @@ class GrampletPane(Gtk.ScrolledWindow):
LOG.warning("Can't make gramplet of type '%s'.", name)
def _button_press(self, obj, event):
ui_def = (
''' <menu id="Popup">
<submenu>
<attribute name="action">win.AddGramplet</attribute>
<attribute name="label" translatable="yes">Add a gramplet</attribute>
%s
</submenu>
<submenu>
<attribute name="action">win.RestoreGramplet</attribute>
<attribute name="label" translatable="yes">'''
'''Restore a gramplet</attribute>
%s
</submenu>
</menu>
''')
menuitem = ('<item>\n'
'<attribute name="action">win.%s</attribute>\n'
'<attribute name="label" translatable="yes">'
'%s</attribute>\n'
'</item>\n')
if is_right_click(event):
self._popup_xy = (event.x, event.y)
uiman = self.uistate.uimanager
ag_menu = uiman.get_widget('/GrampletPopup/AddGramplet')
if ag_menu:
qr_menu = ag_menu.get_submenu()
qr_menu = Gtk.Menu()
names = [gplug.name for gplug in PLUGMAN.get_reg_gramplets()
if gplug.navtypes == []
or 'Dashboard' in gplug.navtypes]
names.sort()
actions = []
r_menuitems = ''
a_menuitems = ''
names = [gplug.name for gplug in PLUGMAN.get_reg_gramplets()
if gplug.navtypes == []
or 'Dashboard' in gplug.navtypes]
names.sort()
for name in names:
action_name = name.replace(' ', '-')
a_menuitems += menuitem % (action_name, name)
actions.append((action_name,
make_callback(self.add_gramplet, name)))
names = [gramplet.title for gramplet in self.closed_gramplets]
names.extend(opts["title"] for opts in self.closed_opts)
names.sort()
if len(names) > 0:
for name in names:
add_menuitem(qr_menu, name, None,
self.add_gramplet)
ag_menu.set_submenu(qr_menu)
rg_menu = uiman.get_widget('/GrampletPopup/RestoreGramplet')
if rg_menu:
qr_menu = rg_menu.get_submenu()
if qr_menu is not None:
rg_menu.set_submenu(None)
names = [gramplet.title for gramplet in self.closed_gramplets]
names.extend(opts["title"] for opts in self.closed_opts)
names.sort()
if len(names) > 0:
qr_menu = Gtk.Menu()
for name in names:
add_menuitem(qr_menu, name, None,
self.restore_gramplet)
rg_menu.set_submenu(qr_menu)
self.menu = uiman.get_widget('/GrampletPopup')
if self.menu:
#GTK3 does not show the popup, workaround: menu as attribute
self.menu.popup(None, None, None, None, event.button, event.time)
action_name = name.replace(' ', '-')
r_menuitems += menuitem % (action_name, name)
actions.append((action_name,
make_callback(self.restore_gramplet,
name)))
if self.at_popup_action:
uiman.remove_ui(self.at_popup_menu)
uiman.remove_action_group(self.at_popup_action)
self.at_popup_action = ActionGroup('AtPopupActions',
actions)
uiman.insert_action_group(self.at_popup_action)
self.at_popup_menu = uiman.add_ui_from_string([
ui_def % (a_menuitems, r_menuitems)])
uiman.update_menu()
menu = uiman.get_widget('Popup')
popup_menu = Gtk.Menu.new_from_model(menu)
popup_menu.attach_to_widget(obj, None)
popup_menu.show_all()
if Gtk.MINOR_VERSION < 22:
# ToDo The following is reported to work poorly with Wayland
popup_menu.popup(None, None, None, None,
event.button, event.time)
else:
popup_menu.popup_at_pointer(event)
return True
return False
+4 -1
View File
@@ -70,7 +70,10 @@ class ShortlistComboEntry(ValidatedComboEntry):
"""
__gtype_name__ = "ShortlistComboEntry"
def __init__(self, items, shortlist=True, validator=None):
def __init__(self):
pass
def init(self, items, shortlist=True, validator=None):
if not items:
raise ValueError
-76
View File
@@ -1,76 +0,0 @@
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2008 Zsolt Foldvari
#
# 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.
#
"Separator classes used for Toolbar."
__all__ = ["SpringSeparatorAction", "SpringSeparatorToolItem"]
#-------------------------------------------------------------------------
#
# Python modules
#
#-------------------------------------------------------------------------
import logging
_LOG = logging.getLogger(".widgets.springseparator")
#-------------------------------------------------------------------------
#
# GTK modules
#
#-------------------------------------------------------------------------
from gi.repository import Gtk
#-------------------------------------------------------------------------
#
# SpringSeparatorToolItem class
#
#-------------------------------------------------------------------------
class SpringSeparatorToolItem(Gtk.SeparatorToolItem):
"""Custom separator toolitem.
Its only purpose is to push following tool items to the right end
of the toolbar.
"""
__gtype_name__ = "SpringSeparatorToolItem"
def __init__(self):
Gtk.SeparatorToolItem.__init__(self)
self.set_draw(False)
self.set_expand(True)
#-------------------------------------------------------------------------
#
# SpringSeparatorAction class
#
#-------------------------------------------------------------------------
class SpringSeparatorAction(Gtk.Action):
"""Custom Action to hold a SpringSeparatorToolItem."""
__gtype_name__ = "SpringSeparatorAction"
def __init__(self, name, label, tooltip, stock_id):
Gtk.Action.__init__(self, name=name, label=label,
tooltip=tooltip, stock_id=stock_id)
## TODO GTK3, How to connect these? Used in styledtexteditor
##SpringSeparatorToolItem.set_related_action(SpringSeparatorAction)
##deprecated: SpringSeparatorAction.set_tool_item_type(SpringSeparatorToolItem)
+236 -183
View File
@@ -43,6 +43,8 @@ from gi.repository import GObject
from gi.repository import Gdk
from gi.repository import Gtk
from gi.repository import Pango
from gi.repository.Gio import SimpleActionGroup
from gi.repository.GLib import Variant
#-------------------------------------------------------------------------
#
@@ -55,15 +57,12 @@ from .styledtextbuffer import (ALLOWED_STYLES,
MATCH_FLAVOR, MATCH_STRING,
LinkTag)
from .undoablestyledbuffer import UndoableStyledBuffer
from .valueaction import ValueAction
from .toolcomboentry import ToolComboEntry
from .springseparator import SpringSeparatorAction
from ..spell import Spell
from ..display import display_url
from ..utils import SystemFonts, match_primary_mask, get_link_color
from gramps.gen.config import config
from gramps.gen.constfunc import has_display, mac
from ..actiongroup import ActionGroup
from ..uimanager import ActionGroup
#-------------------------------------------------------------------------
#
@@ -75,33 +74,141 @@ if has_display():
HAND_CURSOR = Gdk.Cursor.new_for_display(display, Gdk.CursorType.HAND2)
REGULAR_CURSOR = Gdk.Cursor.new_for_display(display, Gdk.CursorType.XTERM)
FORMAT_TOOLBAR = '''
<ui>
<toolbar name="ToolBar">
<toolitem action="%d"/>
<toolitem action="%d"/>
<toolitem action="%d"/>
<toolitem action="%d"/>
<toolitem action="%d"/>
<toolitem action="Undo"/>
<toolitem action="Redo"/>
<toolitem action="%d"/>
<toolitem action="%d"/>
<toolitem action="%d"/>
<toolitem action="spring"/>
<toolitem action="clear"/>
</toolbar>
</ui>
''' % (StyledTextTagType.ITALIC,
StyledTextTagType.BOLD,
StyledTextTagType.UNDERLINE,
StyledTextTagType.FONTFACE,
StyledTextTagType.FONTSIZE,
StyledTextTagType.FONTCOLOR,
StyledTextTagType.HIGHLIGHT,
StyledTextTagType.LINK,
)
FORMAT_TOOLBAR = (
'''<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkToolbar" id="ToolBar">
<property name="hexpand">True</property>
<property name="toolbar-style">GTK_TOOLBAR_ICONS</property>
<style>
<class name="primary-toolbar"/>
</style>
<child>
<object class="GtkToggleToolButton">
<property name="icon-name">format-text-italic</property>
<property name="action-name">ste.ITALIC</property>
<property name="tooltip_text" translatable="yes">Italic</property>
</object>
<packing>
<property name="homogeneous">False</property>
</packing>
</child>
<child>
<object class="GtkToggleToolButton">
<property name="icon-name">format-text-bold</property>
<property name="action-name">ste.BOLD</property>
<property name="tooltip_text" translatable="yes">Bold</property>
</object>
<packing>
<property name="homogeneous">False</property>
</packing>
</child>
<child>
<object class="GtkToggleToolButton">
<property name="icon-name">format-text-underline</property>
<property name="action-name">ste.UNDERLINE</property>
<property name="tooltip_text" translatable="yes">Underline</property>
</object>
<packing>
<property name="homogeneous">False</property>
</packing>
</child>
<child>
<object class="GtkToolItem">
<property name="tooltip_text" translatable="yes">Font family</property>
<child>
<object class="ShortlistComboEntry" id="Fontface"></object>
</child>
</object>
</child>
<child>
<object class="GtkToolItem">
<property name="tooltip_text" translatable="yes">Font size</property>
<child>
<object class="ShortlistComboEntry" id="Fontsize"></object>
</child>
</object>
</child>
<child>
<object class="GtkToolButton">
<property name="icon-name">edit-undo</property>
<property name="action-name">ste.STUndo</property>
<property name="tooltip_text" translatable="yes">Undo</property>
<property name="label" translatable="yes">Undo</property>
</object>
<packing>
<property name="homogeneous">False</property>
</packing>
</child>
<child>
<object class="GtkToolButton">
<property name="icon-name">edit-redo</property>
<property name="action-name">ste.STRedo</property>
<property name="tooltip_text" translatable="yes">Redo</property>
<property name="label" translatable="yes">Redo</property>
</object>
<packing>
<property name="homogeneous">False</property>
</packing>
</child>
<child>
<object class="GtkToolButton">
<property name="icon-name">gramps-font-color</property>
<property name="action-name">ste.FONTCOLOR</property>
<property name="tooltip_text" translatable="yes">Font Color</property>
<property name="label" translatable="yes">Font Color</property>
</object>
<packing>
<property name="homogeneous">False</property>
</packing>
</child>
<child>
<object class="GtkToolButton">
<property name="icon-name">gramps-font-bgcolor</property>
<property name="action-name">ste.HIGHLIGHT</property>
<property name="tooltip_text" translatable="yes">'''
'''Background Color</property>
<property name="label" translatable="yes">Background Color</property>
</object>
<packing>
<property name="homogeneous">False</property>
</packing>
</child>
<child>
<object class="GtkToolButton">
<property name="icon-name">go-jump</property>
<property name="action-name">ste.LINK</property>
<property name="tooltip_text" translatable="yes">Link</property>
<property name="label" translatable="yes">Link</property>
</object>
<packing>
<property name="homogeneous">False</property>
</packing>
</child>
<child>
<object class="GtkSeparatorToolItem">
<property name="draw">False</property>
<property name="hexpand">True</property>
</object>
<packing>
<property name="expand">True</property>
</packing>
</child>
<child>
<object class="GtkToolButton">
<property name="icon-name">edit-clear</property>
<property name="action-name">ste.CLEAR</property>
<property name="tooltip_text" translatable="yes">'''
'''Clear Markup</property>
<property name="label" translatable="yes">Clear Markup</property>
</object>
<packing>
<property name="homogeneous">False</property>
</packing>
</child>
</object>
</interface>
''')
FONT_SIZES = [8, 9, 10, 11, 12, 13, 14, 16, 18, 20, 22,
24, 26, 28, 32, 36, 40, 48, 56, 64, 72]
@@ -182,6 +289,7 @@ class StyledTextEditor(Gtk.TextView):
self.undo_disabled = self.textbuffer.undo_disabled # see bug 7097
self.textbuffer.connect('style-changed', self._on_buffer_style_changed)
self.textbuffer.connect('changed', self._on_buffer_changed)
self.undo_action = self.redo_action = None
Gtk.TextView.__init__(self)
self.set_buffer(self.textbuffer)
@@ -194,9 +302,9 @@ class StyledTextEditor(Gtk.TextView):
self._init_url_match()
self.url_match = None
self.toolbar = self._create_toolbar()
self.spellcheck = Spell(self)
self._internal_style_change = False
self.uimanager = None
self._connect_signals()
@@ -236,30 +344,6 @@ class StyledTextEditor(Gtk.TextView):
window.set_cursor(REGULAR_CURSOR)
self.url_match = None
def on_key_press_event(self, widget, event):
"""Signal handler.
Handle formatting shortcuts.
"""
if ((Gdk.keyval_name(event.keyval) == 'Z') and
match_primary_mask(event.get_state(), Gdk.ModifierType.SHIFT_MASK)):
self.redo()
return True
elif ((Gdk.keyval_name(event.keyval) == 'z') and
match_primary_mask(event.get_state())):
self.undo()
return True
else:
for accel, accel_name in self.action_accels.items():
key, mod = Gtk.accelerator_parse(accel)
if ((event.keyval == key) and (event.get_state() & mod)):
action_name = accel_name
action = self.action_group.get_action(action_name)
action.activate()
return True
return False
def on_insert_at_cursor(self, widget, string):
"""Signal handler. for debugging only."""
_LOG.debug("Textview insert '%s'" % string)
@@ -423,14 +507,13 @@ class StyledTextEditor(Gtk.TextView):
Reset the undoable buffer
"""
self.textbuffer.reset()
self.undo_action.set_sensitive(False)
self.redo_action.set_sensitive(False)
self.undo_action.set_enabled(False)
self.redo_action.set_enabled(False)
# private methods
def _connect_signals(self):
"""Connect to several signals of the super class Gtk.TextView."""
self.connect('key-press-event', self.on_key_press_event)
self.connect('insert-at-cursor', self.on_insert_at_cursor)
self.connect('delete-from-cursor', self.on_delete_from_cursor)
self.connect('paste-clipboard', self.on_paste_clipboard)
@@ -439,104 +522,74 @@ class StyledTextEditor(Gtk.TextView):
self.connect('button-release-event', self.on_button_release_event)
self.connect('populate-popup', self.on_populate_popup)
def _create_toolbar(self):
def create_toolbar(self, uimanager, window):
"""
Create a formatting toolbar.
:returns: toolbar containing text formatting toolitems.
:rtype: Gtk.Toolbar
"""
self.uimanager = uimanager
# build the toolbar
builder = Gtk.Builder.new_from_string(FORMAT_TOOLBAR, -1)
# define the actions...
# ...first the toggle actions, which have a ToggleToolButton as proxy
format_toggle_actions = [
(str(StyledTextTagType.ITALIC), 'format-text-italic', None, None,
_('Italic'), self._on_toggle_action_activate),
(str(StyledTextTagType.BOLD), 'format-text-bold', None, None,
_('Bold'), self._on_toggle_action_activate),
(str(StyledTextTagType.UNDERLINE), 'format-text-underline', None,
None, _('Underline'), self._on_toggle_action_activate),
_actions = [
('ITALIC', self._on_toggle_action_activate, '<PRIMARY>i', False),
('BOLD', self._on_toggle_action_activate, '<PRIMARY>b', False),
('UNDERLINE', self._on_toggle_action_activate, '<PRIMARY>u',
False),
('FONTCOLOR', self._on_action_activate),
('HIGHLIGHT', self._on_action_activate),
('LINK', self._on_link_activate),
('CLEAR', self._format_clear_cb),
('STUndo', self.undo, '<primary>z'),
('STRedo', self.redo, '<primary><shift>z'),
]
self.toggle_actions = [action[0] for action in format_toggle_actions]
# ...then the normal actions, which have a ToolButton as proxy
format_actions = [
(str(StyledTextTagType.FONTCOLOR), 'gramps-font-color',
_('Font Color'), None, _('Font Color'), self._on_action_activate),
(str(StyledTextTagType.HIGHLIGHT), 'gramps-font-bgcolor',
_('Background Color'), None, _('Background Color'),
self._on_action_activate),
(str(StyledTextTagType.LINK), 'go-jump', _('Link'), None,
_('Link'), self._on_link_activate),
('clear', 'edit-clear', _('Clear Markup'), None,
_('Clear Markup'), self._format_clear_cb),
]
# ...last the custom actions, which have custom proxies
default = StyledTextTagType.STYLE_DEFAULT[StyledTextTagType.FONTFACE]
# the following are done manually rather than using actions
fonts = SystemFonts()
fontface_action = ValueAction(str(StyledTextTagType.FONTFACE),
_("Font family"),
default,
ToolComboEntry,
fonts.get_system_fonts(),
False, #editable
True, #shortlist
None) # validator
fontface_action.connect('changed', self._on_valueaction_changed)
fontface = builder.get_object('Fontface')
fontface.init(fonts.get_system_fonts(), shortlist=True, validator=None)
fontface.set_entry_editable(False)
fontface.connect('changed', make_cb(
self._on_valueaction_changed, StyledTextTagType.FONTFACE))
# set initial value
default = StyledTextTagType.STYLE_DEFAULT[StyledTextTagType.FONTFACE]
self.fontface = fontface.get_child()
self.fontface.set_text(str(default))
fontface.show()
items = FONT_SIZES
fontsize = builder.get_object('Fontsize')
fontsize.init(items, shortlist=False, validator=is_valid_fontsize)
fontsize.set_entry_editable(True)
fontsize.connect('changed', make_cb(
self._on_valueaction_changed, StyledTextTagType.FONTSIZE))
# set initial value
default = StyledTextTagType.STYLE_DEFAULT[StyledTextTagType.FONTSIZE]
fontsize_action = ValueAction(str(StyledTextTagType.FONTSIZE),
_("Font size"),
default,
ToolComboEntry,
items,
True, #editable
False, #shortlist
is_valid_fontsize) #validator
fontsize_action.connect('changed', self._on_valueaction_changed)
spring = SpringSeparatorAction("spring", "", "", None)
# action accelerators
self.action_accels = {
'<PRIMARY>i': str(StyledTextTagType.ITALIC),
'<PRIMARY>b': str(StyledTextTagType.BOLD),
'<PRIMARY>u': str(StyledTextTagType.UNDERLINE),
}
self.fontsize = fontsize.get_child()
self.fontsize.set_text(str(default))
fontsize.show()
# create the action group and insert all the actions
self.action_group = ActionGroup(name='Format')
self.action_group.add_toggle_actions(format_toggle_actions)
self.undo_action = Gtk.Action(name="Undo", label=_('Undo'),
tooltip=_('Undo'))
self.undo_action.set_icon_name('edit-undo')
self.undo_action.connect('activate', self.undo)
self.redo_action = Gtk.Action.new(name="Redo", label=_('Redo'),
tooltip=_('Redo'))
self.redo_action.set_icon_name('edit-redo')
self.redo_action.connect('activate', self.redo)
self.action_group.add_action(self.undo_action)
self.action_group.add_action(self.redo_action)
self.action_group.add_actions(format_actions)
self.action_group.add_action(fontface_action)
self.action_group.add_action(fontsize_action)
self.action_group.add_action(spring)
self.action_group = ActionGroup('Format', _actions, 'ste')
act_grp = SimpleActionGroup()
window.insert_action_group('ste', act_grp)
window.set_application(uimanager.app)
uimanager.insert_action_group(self.action_group, act_grp)
# define the toolbar and create the proxies via ensure_update()
uimanager = Gtk.UIManager()
uimanager.insert_action_group(self.action_group, 0)
uimanager.add_ui_from_string(FORMAT_TOOLBAR)
uimanager.ensure_update()
self.undo_action = uimanager.get_action(self.action_group, "STUndo")
self.redo_action = uimanager.get_action(self.action_group, "STRedo")
# allow undo/redo to see actions if editable.
self.textbuffer.connect('changed', self._on_buffer_changed)
# undo/redo are initially greyed out, until something is changed
self.undo_action.set_enabled(False)
self.redo_action.set_enabled(False)
# get the toolbar and set it's style
toolbar = uimanager.get_widget('/ToolBar')
toolbar.set_style(Gtk.ToolbarStyle.ICONS)
self.undo_action.set_sensitive(False)
self.redo_action.set_sensitive(False)
toolbar = builder.get_object('ToolBar')
return toolbar
return toolbar, self.action_group
def set_transient_parent(self, parent=None):
self.transient_parent = parent
@@ -582,21 +635,22 @@ class StyledTextEditor(Gtk.TextView):
# Callback functions
def _on_toggle_action_activate(self, action):
def _on_toggle_action_activate(self, action, value):
"""
Toggle a style.
Toggle styles are e.g. 'bold', 'italic', 'underline'.
"""
action.set_state(value)
if self._internal_style_change:
return
style = int(action.get_name())
value = action.get_active()
_LOG.debug("applying style '%d' with value '%s'" % (style, str(value)))
self.textbuffer.apply_style(style, value)
style = action.get_name()
value = value.get_boolean()
_LOG.debug("applying style '%s' with value '%s'" % (style, str(value)))
self.textbuffer.apply_style(getattr(StyledTextTagType, style), value)
def _on_link_activate(self, action):
def _on_link_activate(self, action, value):
"""
Create a link of a selected region of text.
"""
@@ -633,10 +687,9 @@ class StyledTextEditor(Gtk.TextView):
else:
tag.data = uri
def _on_action_activate(self, action):
def _on_action_activate(self, action, value):
"""Apply a format set from a Gtk.Action type of action."""
style = int(action.get_name())
style = getattr(StyledTextTagType, action.get_name())
current_value = self.textbuffer.get_style_at_cursor(style)
if style == StyledTextTagType.FONTCOLOR:
@@ -668,14 +721,12 @@ class StyledTextEditor(Gtk.TextView):
(style, str(value)))
self.textbuffer.apply_style(style, value)
def _on_valueaction_changed(self, action):
"""Apply a format set by a ValueAction type of action."""
def _on_valueaction_changed(self, obj, style):
"""Apply a format set by a ShortListComboEntry."""
if self._internal_style_change:
return
style = int(action.get_name())
value = action.get_value()
value = obj.get_active_data()
try:
value = StyledTextTagType.STYLE_TYPE[style](value)
_LOG.debug("applying style '%d' with value '%s'" %
@@ -685,7 +736,7 @@ class StyledTextEditor(Gtk.TextView):
_LOG.debug("unable to convert '%s' to '%s'" %
(value, StyledTextTagType.STYLE_TYPE[style]))
def _format_clear_cb(self, action):
def _format_clear_cb(self, action, value):
"""
Remove all formats from the selection or from all.
@@ -709,24 +760,27 @@ class StyledTextEditor(Gtk.TextView):
def _on_buffer_changed(self, buffer):
"""synchronize the undo/redo buttons with what is possible"""
self.undo_action.set_sensitive(self.textbuffer.can_undo)
self.redo_action.set_sensitive(self.textbuffer.can_redo)
if self.undo_action:
self.undo_action.set_enabled(self.textbuffer.can_undo)
self.redo_action.set_enabled(self.textbuffer.can_redo)
def _on_buffer_style_changed(self, buffer, changed_styles):
"""Synchronize actions as the format changes at the buffer's cursor."""
if not self.uimanager:
return # never initialized a toolbar, not editable
types = [StyledTextTagType.ITALIC, StyledTextTagType.BOLD,
StyledTextTagType.UNDERLINE]
self._internal_style_change = True
for style, style_value in changed_styles.items():
if str(style) in self.toggle_actions:
action = self.action_group.get_action(str(style))
self._internal_style_change = True
action.set_active(style_value)
self._internal_style_change = False
if ((style == StyledTextTagType.FONTFACE) or
(style == StyledTextTagType.FONTSIZE)):
action = self.action_group.get_action(str(style))
self._internal_style_change = True
action.set_value(style_value)
self._internal_style_change = False
if style in types:
action = self.uimanager.get_action(
self.action_group, str(StyledTextTagType(style)).upper())
action.change_state(Variant.new_boolean(style_value))
elif (style == StyledTextTagType.FONTFACE):
self.fontface.set_text(style_value)
elif style == StyledTextTagType.FONTSIZE:
self.fontsize.set_text(str(style_value))
self._internal_style_change = False
def _spell_change_cb(self, menuitem, spellcheck):
"""Set spell checker spellcheck according to user selection."""
@@ -818,20 +872,17 @@ class StyledTextEditor(Gtk.TextView):
start, end = self.textbuffer.get_bounds()
return self.textbuffer.get_text(start, end, True)
def get_toolbar(self):
"""
Get the formatting toolbar of the editor.
:returns: toolbar widget to use as formatting GUI.
:rtype: Gtk.Toolbar
"""
return self.toolbar
def undo(self, obj=None):
def undo(self, *obj):
self.textbuffer.undo()
def redo(self, obj=None):
def redo(self, *obj):
self.textbuffer.redo()
#-------------------------------------------------------------------------
#
# Module functions
#
#-------------------------------------------------------------------------
def uri_dialog(self, uri, callback):
"""
@@ -851,12 +902,14 @@ def uri_dialog(self, uri, callback):
uri = "gramps://%s/handle/%s" % (object_class, handle)
EditLink(obj.dbstate, obj.uistate, obj.track, uri, callback)
#-------------------------------------------------------------------------
#
# Module functions
#
#-------------------------------------------------------------------------
def is_valid_fontsize(size):
"""Validator function for font size selector widget."""
return (size > 0) and (size < 73)
def make_cb(func, value):
"""
Generates a callback function based off the passed arguments
"""
return lambda x: func(x, value)
-78
View File
@@ -1,78 +0,0 @@
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2008 Zsolt Foldvari
#
# 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.
#
"ToolComboEntry class."
__all__ = ["ToolComboEntry"]
#-------------------------------------------------------------------------
#
# Python modules
#
#-------------------------------------------------------------------------
import logging
_LOG = logging.getLogger(".widgets.toolcomboentry")
#-------------------------------------------------------------------------
#
# GTK modules
#
#-------------------------------------------------------------------------
#from gi.repository import GObject
from gi.repository import Gtk
#-------------------------------------------------------------------------
#
# Gramps modules
#
#-------------------------------------------------------------------------
from .valuetoolitem import ValueToolItem
from .shortlistcomboentry import ShortlistComboEntry
#-------------------------------------------------------------------------
#
# ToolComboEntry class
#
#-------------------------------------------------------------------------
class ToolComboEntry(ValueToolItem):
"""Tool bar item containing a ShortlistComboEntry widget."""
__gtype_name__ = "ToolComboEntry"
def _create_widget(self, items, editable, shortlist=True, validator=None):
self.set_border_width(2)
self.set_homogeneous(False)
self.set_expand(False)
combo = ShortlistComboEntry(items, shortlist, validator)
if (Gtk.MAJOR_VERSION, Gtk.MINOR_VERSION) < (3, 20):
combo.set_focus_on_click(False)
else:
Gtk.Widget.set_focus_on_click(combo, False)
combo.set_entry_editable(editable)
combo.show()
self.add(combo)
combo.connect('changed', self._on_widget_changed)
def set_value(self, value):
self.get_child().set_active_data(value)
def get_value(self):
return self.get_child().get_active_data()
+5 -1
View File
@@ -65,7 +65,8 @@ class ValidatedComboEntry(Gtk.ComboBox):
__gtype_name__ = "ValidatedComboEntry"
def __init__(self, datatype, model=None, column=-1, validator=None, width=-1):
Gtk.ComboBox.__init__(self, model=model)
Gtk.ComboBox.__init__(self)
self.set_model(model)
self._entry = Gtk.Entry()
self._entry.set_width_chars(width)
@@ -201,6 +202,9 @@ class ValidatedComboEntry(Gtk.ComboBox):
self._internal_change = True
new_iter = self._is_in_model(new_data)
if new_iter is None:
if self.get_active_iter() is None:
# allows response when changing between two non-model values
self.set_active(0)
self.set_active(-1)
else:
self.set_active_iter(new_iter)
-169
View File
@@ -1,169 +0,0 @@
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2008 Zsolt Foldvari
#
# 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.
#
"ValueAction class."
__all__ = ["ValueAction"]
#-------------------------------------------------------------------------
#
# Python modules
#
#-------------------------------------------------------------------------
import logging
_LOG = logging.getLogger(".widgets.valueaction")
#-------------------------------------------------------------------------
#
# GTK modules
#
#-------------------------------------------------------------------------
from gi.repository import GObject
from gi.repository import Gtk
#-------------------------------------------------------------------------
#
# Gramps modules
#
#-------------------------------------------------------------------------
from .valuetoolitem import ValueToolItem
#-------------------------------------------------------------------------
#
# ValueAction class
#
#-------------------------------------------------------------------------
class ValueAction(Gtk.Action):
"""
Value action class.
(A ValueAction with menu item doesn't make any sense.)
"""
__gtype_name__ = "ValueAction"
__gsignals__ = {
'changed': (GObject.SignalFlags.RUN_FIRST,
None, #return value
()), # arguments
}
def __init__(self, name, tooltip, default, itemtype, *args):
"""
Create a new ValueAction instance.
:param name: the name of the action
:type name: str
:param tooltip: tooltip string
:type tooltip: str
:param default: default value for the action, it will set the type of
the action and thus the type of all the connected
proxies.
:type default: set by itemtype
:param itemtype: default tool item class
:type itemtype: :class:`.ValueToolItem` subclass
:param args: arguments to be passed to the default toolitem class
at creation. see: :meth:`do_create_tool_item`
:type args: list
"""
Gtk.Action.__init__(self, name=name, label='', tooltip=tooltip,
stock_id=None)
self._value = default
self._data_type = type(default)
# have to be remembered, because we can't access
# GtkAction->toolbar_item_type later.
self._default_toolitem_type = itemtype
##TODO GTK3: following is deprecated, must be replaced by
## itemtype.set_related_action(ValueAction) in calling class?
## self.set_tool_item_type(itemtype)
self._args_for_toolitem = args
self._handlers = {}
def do_changed(self):
"""
Default signal handler for 'changed' signal.
Synchronize all the proxies with the active value.
"""
for proxy in self.get_proxies():
proxy.handler_block(self._handlers[proxy])
proxy.set_value(self._value)
proxy.handler_unblock(self._handlers[proxy])
def do_create_tool_item(self):
"""
Create a 'default' toolbar item widget.
Override the default method, to be able to pass the required
parameters to the proxy's constructor.
This method is called from Gtk.UIManager.ensure_update(), when a
'toolitem' is found in the UI definition with a name refering to a
ValueAction. Thus, to use the action via the UIManager a 'default'
toolitem type has to be set with the Gtk.Action.set_tool_item_type()
method, before invoking the Gtk.UIManager.ensure_update() method.
Widgets other than the default type has to be created and added
manually with the Gtk.Action.connect_proxy() method.
:returns: a toolbar item connected to the action.
:rtype: :class:`.ValueToolItem` subclass
"""
proxy = self._default_toolitem_type(self._data_type,
self._args_for_toolitem)
self.connect_proxy(proxy)
return proxy
def _on_proxy_changed(self, proxy):
"""Signal handler for the proxies' 'changed' signal."""
value = proxy.get_value()
if value is not None:
self.set_value(value)
def connect_proxy(self, proxy):
"""
Connect a widget to an action object as a proxy.
:param proxy: widget to be connected
:type proxy: :class:`.ValueToolItem` subclass
"""
if not isinstance(proxy, ValueToolItem):
raise TypeError
# do this before connecting, so that we don't call the handler
proxy.set_value(self._value)
self._handlers[proxy] = proxy.connect('changed', self._on_proxy_changed)
# if this is called the proxy will appear on the proxy list twice. why?
#Gtk.Action.connect_proxy(self, proxy)
def set_value(self, value):
"""Set value to action."""
if not isinstance(value, self._data_type):
raise TypeError
self._value = value
self.emit('changed')
def get_value(self):
"""Get the value from the action."""
return self._value
-92
View File
@@ -1,92 +0,0 @@
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2008 Zsolt Foldvari
#
# 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.
#
"ValueToolItem class."
__all__ = ["ValueToolItem"]
#-------------------------------------------------------------------------
#
# Python modules
#
#-------------------------------------------------------------------------
import logging
_LOG = logging.getLogger(".widgets.valuetoolitem")
#-------------------------------------------------------------------------
#
# GTK modules
#
#-------------------------------------------------------------------------
from gi.repository import GObject
from gi.repository import Gtk
#-------------------------------------------------------------------------
#
# ValueToolItem class
#
#-------------------------------------------------------------------------
class ValueToolItem(Gtk.ToolItem):
"""ValueToolItem is an abstract toolbar proxy for ValueAction.
For each kind of widget a separete tool item proxy has to be
subclassed from this ValueToolItem.
"""
__gtype_name__ = "ValueToolItem"
__gsignals__ = {
'changed': (GObject.SignalFlags.RUN_FIRST,
None, #return value
()), # arguments
}
def __init__(self, data_type, args):
Gtk.ToolItem.__init__(self)
self._data_type = data_type
self._create_widget(*args)
def _on_widget_changed(self, widget):
self.emit('changed')
def _create_widget(self, args):
"""Create the apropriate widget for the actual proxy."""
raise NotImplementedError
def set_value(self, value):
"""Set new value for the proxied widget.
The method is responsible converting the data type between action and
widget.
"""
raise NotImplementedError
def get_value(self):
"""Get value from the proxied widget.
The method is responsible converting the data type between action and
widget.
"""
raise NotImplementedError
+1 -1
View File
@@ -27,7 +27,7 @@ plg.name = _("BSDDB")
plg.name_accell = _("_BSDDB Database")
plg.description = _("Berkeley Software Distribution Database Backend")
plg.version = '1.0'
plg.gramps_target_version = "5.0"
plg.gramps_target_version = "5.1"
plg.status = STABLE
plg.fname = 'bsddb.py'
plg.ptype = DATABASE
+6 -3
View File
@@ -129,9 +129,12 @@ def find_fullname(key, data):
# surname primary,
# surname origin type,
# surname connector)]
fullname_data = [(data[3][5][0][0] + ' ' + data[3][4], # surname givenname
data[3][5][0][1], data[3][5][0][2],
data[3][5][0][3], data[3][5][0][4])]
if data[3][5]: # if Surname available
fullname_data = [(data[3][5][0][0] + ' ' + data[3][4], # combined
data[3][5][0][1], data[3][5][0][2],
data[3][5][0][3], data[3][5][0][4])]
else: # Some importers don't add any Surname at all
fullname_data = [(' ' + data[3][4], '', True, (1, ''), '')]
# ignore if origin type is PATRONYMIC or MATRONYMIC
return __index_surname(fullname_data)
+1 -1
View File
@@ -27,7 +27,7 @@ register(DATABASE,
name_accell=_('_SQLite Database'),
description=_('SQLite Database'),
version='1.0.0',
gramps_target_version='5.0',
gramps_target_version='5.1',
status=STABLE,
fname='sqlite.py',
databaseclass='SQLite',
+1 -1
View File
@@ -21,7 +21,7 @@ from gramps.gen.plug._pluginreg import newplugin, STABLE
from gramps.gen.const import GRAMPS_LOCALE as glocale
_ = glocale.translation.gettext
MODULE_VERSION="5.0"
MODULE_VERSION="5.1"
#------------------------------------------------------------------------
#
+3 -2
View File
@@ -579,7 +579,7 @@ class LaTeXBackend(DocBackend):
LaTeXBackend.ESCAPE_FUNC = lambda x: latexescapeverbatim
def _create_xmltag(self, type, value):
"""
r"""
overwrites the method in DocBackend.
creates the latex tags needed for non bool style types we support:
FONTSIZE : use different \large denomination based
@@ -1267,7 +1267,8 @@ class LaTeXDoc(BaseDoc, TextDoc):
text = re.sub(URL_PATTERN, _CLICKABLE, text)
#hard coded replace of the underline used for missing names/data
text = text.replace('\\_'*13, '\\underline{\hspace{3\\grbaseindent}}')
text = text.replace('\\_' * 13,
'\\underline{\\hspace{3\\grbaseindent}}')
self.emit(text + ' ')
+3 -3
View File
@@ -102,7 +102,7 @@ class RTFDoc(BaseDoc, TextDoc):
'{\\fonttbl\n'
'{\\f0\\froman\\fcharset0\\fprq0 Times New Roman;}\n'
'{\\f1\\fswiss\\fcharset0\\fprq0 Arial;}}\n'
'{\colortbl\n'
'{\\colortbl\n'
)
self.color_map = {}
@@ -442,7 +442,7 @@ class RTFDoc(BaseDoc, TextDoc):
act_height = size[1]
if self.in_table:
self.text += '{\*\shppict{\\pict\\jpegblip'
self.text += '{\\*\\shppict{\\pict\\jpegblip'
self.text += '\\picwgoal%d\\pichgoal%d\n' % (act_width, act_height)
index = 1
for i in buf:
@@ -452,7 +452,7 @@ class RTFDoc(BaseDoc, TextDoc):
index = index+1
self.text += '}}\\par\n'
else:
self.file.write('{\*\shppict{\\pict\\jpegblip')
self.file.write('{\\*\\shppict{\\pict\\jpegblip')
self.file.write('\\picwgoal%d\\pichgoal%d\n' % (act_width, act_height))
index = 1
for i in buf:
+3 -7
View File
@@ -227,13 +227,9 @@ class Calendar(Report):
self.doc.draw_box("CAL-Title", "", 0, 0, width, header, mark)
self.doc.draw_line("CAL-Border", 0, header, width, header)
year = self.year
# TRANSLATORS: see
# http://gramps-project.org/wiki/index.php?title=Translating_Gramps#Translating_dates
# to learn how to select proper inflection for your language.
title = self._("{long_month} {year}").format(
long_month = self._ldd.long_months[month],
year = year
).capitalize()
# assume every calendar header in the world is "<month-name> <year>"
title = "%s %s" % (self._ldd.long_months[month].capitalize(),
self._get_date(Date(self.year))) # localized year
mark = IndexMark(title, INDEX_TYPE_TOC, 2)
font_height = pt2cm(ptitle.get_font().get_size())
self.doc.center_text("CAL-Title", title,

Some files were not shown because too many files have changed in this diff Show More