Zim

Merge lp:~beutlin/zim/table-plugin into lp:~jaap.karssenberg/zim/pyzim

Proposed by Tobias Haupenthal
Status: Merged
Merged at revision: 758
Proposed branch: lp:~beutlin/zim/table-plugin
Merge into: lp:~jaap.karssenberg/zim/pyzim
Diff against target: 5325 lines (+2737/-441)
27 files modified
data/manual/Plugins.txt (+1/-0)
data/manual/Plugins/Journal.txt (+1/-1)
data/manual/Plugins/Table_Editor.txt (+21/-0)
data/manual/Plugins/Table_Editor/Basic_Table_Operations.txt (+41/-0)
data/manual/Plugins/Table_Editor/Superior_Table_Operations.txt (+46/-0)
tests/__init__.py (+1/-1)
tests/data/formats/export.html (+25/-0)
tests/data/formats/export.markdown (+8/-0)
tests/data/formats/export.rst (+12/-0)
tests/data/formats/export.tex (+16/-0)
tests/data/formats/parsetree.xml (+2/-0)
tests/data/formats/plain.txt (+12/-0)
tests/data/formats/wiki.txt (+7/-0)
tests/objectmanager.py (+1/-1)
tests/tableeditor.py (+84/-0)
translations/zim.pot (+603/-415)
zim/formats/__init__.py (+167/-1)
zim/formats/html.py (+45/-0)
zim/formats/latex.py (+24/-0)
zim/formats/markdown.py (+28/-1)
zim/formats/plain.py (+33/-0)
zim/formats/rst.py (+33/-0)
zim/formats/wiki.py (+125/-3)
zim/gui/objectmanager.py (+1/-1)
zim/gui/pageview.py (+233/-13)
zim/objectmanager.py (+8/-4)
zim/plugins/tableeditor.py (+1159/-0)
To merge this branch: bzr merge lp:~beutlin/zim/table-plugin
Reviewer Review Type Date Requested Status
Tobias Haupenthal (community) Needs Resubmitting
Jaap Karssenberg Needs Information
Review via email: mp+251673@code.launchpad.net

Description of the change

Hello,
currently I've created a table plugin for zim and wish to get this into the main branch.
Please have a look at it.

Tobias Haupenthal

To post a comment you must log in.
Revision history for this message
Murat Güven (murat-gueven) wrote :

Hi,

could you provide your plugin for testing?

Regards,
Murat

-----Ursprüngliche Nachricht-----
Von: <email address hidden> [mailto:<email address hidden>] Im Auftrag von Tobias Haupenthal
Gesendet: Mittwoch, 4. März 2015 00:58
An: <email address hidden>; Jaap Karssenberg
Betreff: [Merge] lp:~beutlin/zim/table-plugin into lp:zim

Tobias Haupenthal has proposed merging lp:~beutlin/zim/table-plugin into lp:zim.

Requested reviews:
  Jaap Karssenberg (jaap.karssenberg)

For more details, see:
https://code.launchpad.net/~beutlin/zim/table-plugin/+merge/251673

Hello,
currently I've created a table plugin for zim and wish to get this into the main branch.
Please have a look at it.

Tobias Haupenthal
--
You are subscribed to branch lp:zim.

Revision history for this message
Jaap Karssenberg (jaap.karssenberg) wrote :

Hi Tobias,

Just checked out your branch, and my first reaction is that I'm highly impressed with the work you put into this - it really looks very polished.

I have a few minor change requests that I will come back on soon. In the mean time I will start testing and reviewing the code.

I feel this feature alone is reason enough to publish a new release asap.

Regards,

Jaap

Revision history for this message
Jaap Karssenberg (jaap.karssenberg) wrote :

Hi Tobias,

After studying the code in more detail I think it can be merged right away.

My only point of discussion is the format of tables in the default wiki format. I wonder of the choice of using the "{{{table ... }}}" object syntax is the right choice. Especially sine you build the table format more as a core structure into the parser and only the widget is in the plugin.

My proposal then would be to support tables in the native wiki syntax without the "{{{ ... }}}" object wrapper. I would propose to use the same format as in markdown [1] and possibly also support the wiki creole syntax [2]. Fine to allow reading the object syntax, but when writing we should just use the native format.

Wanted to make the change myself and then merge. But I don't know what to do with the "wraps" attribute. What would be a logical syntax to add that in a markdown like table format ?

Regards,

Jaap

[1] https://help.github.com/articles/github-flavored-markdown/
[2] http://www.wikicreole.org/wiki/Creole1.0#Tables

review: Needs Information
Revision history for this message
Tobias Haupenthal (beutlin) wrote :

Hi Jaap,
thank you for your feedback.

One of the most important aspects is indeed the syntax for the table.
A futural feature might be including external files directly. With the object-oriented form I could use {{{table file="./data.csv"}}}. But with a simple markdown syntax it could be nearly impossible.
Do you have any good idea to circumvent that issue? Maybe using a different plugin?

Wrapping mode can be implemented with two colons, whereas normal mode is one colon.
center - left - right alignment

| h1 | h2 | h3 |
|::-:|::--|--::|

vs.
| h1 | h2 | h3 |
|:--:|:---|---:|

Regards,
Tobias

Revision history for this message
Jaap Karssenberg (jaap.karssenberg) wrote :

Hi Tobias,

Using markdown syntax now for inline tables will not prevent you from using
object syntax later for extensions line including CSV files. In fact, I
would highly encourage going that way :)

In my vision, at some point of time there will be two table widgets. One
for "data", and one for "grid" tables. The widget you wrote based on the
gtk Treeview is a very good start for a "data" table. It would be perfect
for rendering CSV, and you could extend it by e.g. defining data types per
column (boolean checkboxes, integers with spinner, etc.).

The "grid" table will be really text driven and could allow things like
row-span and col-span which a "data" table will not have. I think the
inline markdown format is really a "grid" table. However since we only have
your widget for now, we use that for all tables. (Later on it could still
be a user preference what table rendering to use by default.)

So I would support inline markdown tables and use that as the default for
"all text" tables. For CSV and similar data, definitely the object syntax
is the right thing to do. I would recommend to use the object type
"data-table" instead of "table", so we can differentiate later. I can even
imagine:

  {{{data-tabel format=csv
  head1,head2,head3
  ..., ..., ...
  }}}

The plugin could just define both "table" and "data-table" and write back
in a different format depending on the object type originally defined.

For the wrapping I was thinking to annotate the header line, that keeps the
format backward compatible with markdown rendering.

E.g.

  | h1 < | h2 | h3 |
  |:-:|:--|--:|

Where the "<" after a heading means wrap. Maybe you even want to go with
"<10" for wrapping at (approximately) 10 characters width.

My rationale for this is that I have plans to support Markdown as native
format as well, so I try to do stuff in a more or less compatible way. With
this syntax a markdown parser will still render the table, be it with the
annotation still visible in the heading.

Does that make sense - or am I making things to complicated?

Regards,

Jaap

On Mon, Mar 23, 2015 at 7:39 PM, Tobias Haupenthal <email address hidden>
wrote:

> Hi Jaap,
> thank you for your feedback.
>
> One of the most important aspects is indeed the syntax for the table.
> A futural feature might be including external files directly. With the
> object-oriented form I could use {{{table file="./data.csv"}}}. But with a
> simple markdown syntax it could be nearly impossible.
> Do you have any good idea to circumvent that issue? Maybe using a
> different plugin?
>
> Wrapping mode can be implemented with two colons, whereas normal mode is
> one colon.
> center - left - right alignment
>
> | h1 | h2 | h3 |
> |::-:|::--|--::|
>
> vs.
> | h1 | h2 | h3 |
> |:--:|:---|---:|
>
>
> Regards,
> Tobias
>
> --
> https://code.launchpad.net/~beutlin/zim/table-plugin/+merge/251673
> You are reviewing the proposed merge of lp:~beutlin/zim/table-plugin into
> lp:zim.
>

Revision history for this message
Tobias Haupenthal (beutlin) wrote :

Hello Jaap,
your mail sounds well thought-out. I like your vision.
Please give me any indication if you prefer that I do these syntax changes.

Many thanks for your code review!

Tobias

Revision history for this message
Jaap Karssenberg (jaap.karssenberg) wrote :

Hi Tobias,

I was going to do a quick patch myself, but didn't get around it. Will be
offline for the next few days. So if you happen to have some time, please
go ahead and make the patch. Otherwise I'll get back to it in a week or two.

Regards,

Jaap

On Tue, Mar 24, 2015 at 11:14 PM, Tobias Haupenthal <email address hidden>
wrote:

> Hello Jaap,
> your mail sounds well thought-out. I like your vision.
> Please give me any indication if you prefer that I do these syntax changes.
>
> Many thanks for your code review!
>
> Tobias
> --
> https://code.launchpad.net/~beutlin/zim/table-plugin/+merge/251673
> You are reviewing the proposed merge of lp:~beutlin/zim/table-plugin into
> lp:zim.
>

lp:~beutlin/zim/table-plugin updated
759. By Tobias Haupenthal <email address hidden>

sourcecode of table now markdown-like; wrapping and alignment syntax changed

Revision history for this message
Tobias Haupenthal (beutlin) wrote :

Hi Jaap,
the branch was updated for the new markdown related syntax.
Maybe you can have a look at it again.

Regards,
Tobias

review: Needs Resubmitting
Revision history for this message
Jaap Karssenberg (jaap.karssenberg) wrote :

Done

On Tue, Apr 14, 2015 at 8:43 PM, Tobias Haupenthal <email address hidden>
wrote:

> Review: Resubmit
>
> Hi Jaap,
> the branch was updated for the new markdown related syntax.
> Maybe you can have a look at it again.
>
> Regards,
> Tobias
> --
> https://code.launchpad.net/~beutlin/zim/table-plugin/+merge/251673
> You are reviewing the proposed merge of lp:~beutlin/zim/table-plugin into
> lp:zim.
>

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'data/manual/Plugins.txt'
2--- data/manual/Plugins.txt 2014-10-10 19:35:17 +0000
3+++ data/manual/Plugins.txt 2015-04-04 11:26:41 +0000
4@@ -27,6 +27,7 @@
5 * [[+Sequence Diagram Editor|Sequence Diagram Editor]]
6 * [[+Source View|Source View]]
7 * [[+Spell Checker|Spell Checker]]
8+* [[+Table Editor|Table Editor]]
9 * [[+Table Of Contents|Table Of Contents]]
10 * [[+Tags|Tags]]
11 * [[+Task List|Task List]]
12
13=== modified file 'data/manual/Plugins/Journal.txt'
14--- data/manual/Plugins/Journal.txt 2014-08-28 13:08:14 +0000
15+++ data/manual/Plugins/Journal.txt 2015-04-04 11:26:41 +0000
16@@ -15,7 +15,7 @@
17 This plugin has the following options:
18
19 The option **Show calendar in sidepane instead of as dialog** does just that; if enabled the calendar widget is embedded at the top of the side pane. Otherwise the calendar is shown in a separate dialog when you click the toolbar button. If the calendar is embedded in the side pane, the option **Position in the window** determines in which side pane the calendar is shown.
20-
21+The option **Expand journal page in index when opened** expands the index-tree if a date is opened.
22 The option **Use a page for each: ...** allows you to set the calendar to either use a separate page for each day, each week, each month or even each year.
23
24 The **Section** controls where the calendar pages are stored.
25
26=== added directory 'data/manual/Plugins/Table_Editor'
27=== added file 'data/manual/Plugins/Table_Editor.txt'
28--- data/manual/Plugins/Table_Editor.txt 1970-01-01 00:00:00 +0000
29+++ data/manual/Plugins/Table_Editor.txt 2015-04-04 11:26:41 +0000
30@@ -0,0 +1,21 @@
31+Content-Type: text/x-zim-wiki
32+Wiki-Format: zim 0.4
33+
34+====== Table Editor ======
35+
36+{{./table_demo.png}}[Only a picture]
37+This plugin allows you to integrate a table into the zim editor.
38+**Dependencies:** This plugin has no additional dependencies
39+
40+===== Options =====
41+Before you can use this plugin, it must be enabled in **Insert->Preferences** on the tab Plugins.
42+Here you can configure it with these options:
43+
44+**Show helper toolbar**: Displays a menu with buttons beneath the table, if it is selected.
45+{{./table_toolbar.png}}{{./table_footer.png}}
46+**Grid lines**: Enables or disables lines between table cells
47+{{./with_borders.png}}{{./without_borders.png}}
48+
49+===== Read more =====
50+[[+Basic Table Operations|Basic Table Operations]]
51+[[+Superior Table Operations|Superior Table Operations]]
52
53=== added directory 'data/manual/Plugins/Table_Editor/Basic_Table_Operations'
54=== added file 'data/manual/Plugins/Table_Editor/Basic_Table_Operations.txt'
55--- data/manual/Plugins/Table_Editor/Basic_Table_Operations.txt 1970-01-01 00:00:00 +0000
56+++ data/manual/Plugins/Table_Editor/Basic_Table_Operations.txt 2015-04-04 11:26:41 +0000
57@@ -0,0 +1,41 @@
58+Content-Type: text/x-zim-wiki
59+Wiki-Format: zim 0.4
60+
61+====== Basic Table Operations ======
62+
63+===== Insertion of a table =====
64+
65+==== Insert directly within textarea ====
66+
67+'''
68+|Header 1|Header 2|Header 3|
69+|Data 1a|Data 2a|Data 3a|
70+|Data 1b|Data 2b|Data 2b|
71+
72+Text after table. Table has to end with a blank line.
73+'''
74+
75+Press CTRL+R or open menu item **View->reload**, so that the table can be displayed as widget.
76+
77+==== Use the insert-table button ====
78+{{./insert-table.png}} or press the menu item **Insert -> Table**
79+After this step you can define the table titles with following Options:
80+**Title**: Column title
81+**Alignment**: Alignment of the cell (left, center, right)
82+**Automatic Wrapping**: Column Cells with long text should be **automatic wrapped** to new lines.
83+{{./table_settings_dialog.png}}
84+
85+==== Add row / Change table layout ====
86+Press the right button on the area of the table - but not on the header - and select in the popup menu some item.
87+{{./table_popup.png}}
88+
89+===== Enter text in a cell =====
90+1. Click one time on a cell, wait one second.
91+2. Click again on the cell.
92+3. Now it opens a textfield where you can fill the cell.
93+4. Press <ENTER> and the text will be fixed.
94+ or Press <ESC> and the inserted text will be ignored.
95+
96+
97+===== Sorting =====
98+Click one time on the column title, which you want to sort.
99
100=== added file 'data/manual/Plugins/Table_Editor/Basic_Table_Operations/insert-table.png'
101Binary files data/manual/Plugins/Table_Editor/Basic_Table_Operations/insert-table.png 1970-01-01 00:00:00 +0000 and data/manual/Plugins/Table_Editor/Basic_Table_Operations/insert-table.png 2015-04-04 11:26:41 +0000 differ
102=== added file 'data/manual/Plugins/Table_Editor/Basic_Table_Operations/table_popup.png'
103Binary files data/manual/Plugins/Table_Editor/Basic_Table_Operations/table_popup.png 1970-01-01 00:00:00 +0000 and data/manual/Plugins/Table_Editor/Basic_Table_Operations/table_popup.png 2015-04-04 11:26:41 +0000 differ
104=== added file 'data/manual/Plugins/Table_Editor/Basic_Table_Operations/table_settings_dialog.png'
105Binary files data/manual/Plugins/Table_Editor/Basic_Table_Operations/table_settings_dialog.png 1970-01-01 00:00:00 +0000 and data/manual/Plugins/Table_Editor/Basic_Table_Operations/table_settings_dialog.png 2015-04-04 11:26:41 +0000 differ
106=== added directory 'data/manual/Plugins/Table_Editor/Superior_Table_Operations'
107=== added file 'data/manual/Plugins/Table_Editor/Superior_Table_Operations.txt'
108--- data/manual/Plugins/Table_Editor/Superior_Table_Operations.txt 1970-01-01 00:00:00 +0000
109+++ data/manual/Plugins/Table_Editor/Superior_Table_Operations.txt 2015-04-04 11:26:41 +0000
110@@ -0,0 +1,46 @@
111+Content-Type: text/x-zim-wiki
112+Wiki-Format: zim 0.4
113+
114+====== Superior Table Operations ======
115+
116+===== Markup in a cell =====
117+The main toolbar buttons for formatting are disabled. You have to know the special syntax and insert it directly within the textbox.
118+While editing: {{./cell_editing_with_text_formatting.png}}
119+After formatting: {{./cell_after_text_formatting.png}}
120+
121+==== Common formats ====
122+**bold**, //italic//, __highlight__, ~~strike through and~~ and ''verbatim''
123+
124+'''
125+**bold**, //italic//, __highlight__, ~~strike through~~ and ''verbatim''
126+'''
127+
128+
129+==== Special characters ====
130+\\n Newline
131+\| | Character
132+
133+==== Links in a cell ====
134+'''
135+[[http://www.python.org]]
136+[[foo@bar.org]]
137+[[foo]] Internal Link
138+'''
139+
140+Note: Only **one link per cell** can be opened. The first one is taken.
141+ You can not use a special link text. The link text is the url, the mail-address or the Title of the linked page.
142+A link can be accessed with:
143+* ''RIGHT MOUSE-BUTTON-CLICK'' on cell with the link and selection of the menu item //Open cell in content link//
144+* ''CTRL + LEFT MOUSE-BUTTON-CLICK'' on a cell
145+
146+See also: [[:Help:Links|Syntax for Links]]
147+
148+===== Exporting: =====
149+Exporting filters for Plain, HTML, Markdown, reStructuredText, Latex have been also adjusted for the table plugin.
150+
151+===== Limits of widget's integration =====
152+* search / replacing:
153+ * no highlighting
154+ * only first location is found and after that the next one within the main text
155+* Undo / Redo not supported at all
156+* There can not be any element right or left to the table. The table must stand alone.
157
158=== added file 'data/manual/Plugins/Table_Editor/Superior_Table_Operations/cell_after_text_formatting.png'
159Binary files data/manual/Plugins/Table_Editor/Superior_Table_Operations/cell_after_text_formatting.png 1970-01-01 00:00:00 +0000 and data/manual/Plugins/Table_Editor/Superior_Table_Operations/cell_after_text_formatting.png 2015-04-04 11:26:41 +0000 differ
160=== added file 'data/manual/Plugins/Table_Editor/Superior_Table_Operations/cell_editing_with_text_formatting.png'
161Binary files data/manual/Plugins/Table_Editor/Superior_Table_Operations/cell_editing_with_text_formatting.png 1970-01-01 00:00:00 +0000 and data/manual/Plugins/Table_Editor/Superior_Table_Operations/cell_editing_with_text_formatting.png 2015-04-04 11:26:41 +0000 differ
162=== added file 'data/manual/Plugins/Table_Editor/table_demo.png'
163Binary files data/manual/Plugins/Table_Editor/table_demo.png 1970-01-01 00:00:00 +0000 and data/manual/Plugins/Table_Editor/table_demo.png 2015-04-04 11:26:41 +0000 differ
164=== added file 'data/manual/Plugins/Table_Editor/table_footer.png'
165Binary files data/manual/Plugins/Table_Editor/table_footer.png 1970-01-01 00:00:00 +0000 and data/manual/Plugins/Table_Editor/table_footer.png 2015-04-04 11:26:41 +0000 differ
166=== added file 'data/manual/Plugins/Table_Editor/table_toolbar.png'
167Binary files data/manual/Plugins/Table_Editor/table_toolbar.png 1970-01-01 00:00:00 +0000 and data/manual/Plugins/Table_Editor/table_toolbar.png 2015-04-04 11:26:41 +0000 differ
168=== added file 'data/manual/Plugins/Table_Editor/with_borders.png'
169Binary files data/manual/Plugins/Table_Editor/with_borders.png 1970-01-01 00:00:00 +0000 and data/manual/Plugins/Table_Editor/with_borders.png 2015-04-04 11:26:41 +0000 differ
170=== added file 'data/manual/Plugins/Table_Editor/without_borders.png'
171Binary files data/manual/Plugins/Table_Editor/without_borders.png 1970-01-01 00:00:00 +0000 and data/manual/Plugins/Table_Editor/without_borders.png 2015-04-04 11:26:41 +0000 differ
172=== added file 'data/pixmaps/insert-table.png'
173Binary files data/pixmaps/insert-table.png 1970-01-01 00:00:00 +0000 and data/pixmaps/insert-table.png 2015-04-04 11:26:41 +0000 differ
174=== modified file 'tests/__init__.py'
175--- tests/__init__.py 2014-08-20 09:46:08 +0000
176+++ tests/__init__.py 2015-04-04 11:26:41 +0000
177@@ -55,7 +55,7 @@
178 'calendar', 'printtobrowser', 'versioncontrol', 'inlinecalculator',
179 'tasklist', 'tags', 'imagegenerators', 'tableofcontents',
180 'quicknote', 'attachmentbrowser', 'insertsymbol',
181- 'sourceview',
182+ 'sourceview', 'tableeditor',
183 'ipc'
184 ]
185
186
187=== modified file 'tests/data/formats/export.html'
188--- tests/data/formats/export.html 2014-09-04 17:56:31 +0000
189+++ tests/data/formats/export.html 2015-04-04 11:26:41 +0000
190@@ -268,8 +268,33 @@
191 </pre>
192 </div>
193
194+<h2>A table</h2>
195+
196 <br>
197
198+<table>
199+<thead><tr>
200+ <th align="center">H1</th>
201+ <th align="right">H2 h2</th>
202+ <th align="left">H3</th>
203+</tr></thead>
204+<tr>
205+ <td align="center">Column A1</td>
206+ <td align="right">Column A2</td>
207+ <td align="left">a</td>
208+</tr>
209+<tr>
210+ <td align="center">a very long cell</td>
211+ <td align="right"><b>bold text</b></td>
212+ <td align="left">b</td>
213+</tr>
214+<tr>
215+ <td align="center">hyperlinks</td>
216+ <td align="right"><a href="interwiki:wp?wiki" title="wp?wiki" class="interwiki">wp?wiki</a></td>
217+ <td align="left"><a href="http://x.org" title="Xorg" class="http">Xorg</a></td>
218+</tr>
219+</table>
220+
221 <p>
222 ====<br>
223 This is not a header
224
225=== modified file 'tests/data/formats/export.markdown'
226--- tests/data/formats/export.markdown 2014-05-04 19:17:46 +0000
227+++ tests/data/formats/export.markdown 2015-04-04 11:26:41 +0000
228@@ -165,6 +165,14 @@
229 brought countless ills upon
230 the Achaeans.
231
232+A table
233+-------
234+
235+| H1 | H2 h2 | H3 |
236+|:----------------:|-----------------------------:|:---------------------|
237+| Column A1 | Column A2 | a |
238+| a very long cell | **bold text** | b |
239+| hyperlinks | [wp?wiki](interwiki:wp?wiki) | [Xorg](http://x.org) |
240
241 ====
242 This is not a header
243
244=== modified file 'tests/data/formats/export.rst'
245--- tests/data/formats/export.rst 2014-05-04 19:17:46 +0000
246+++ tests/data/formats/export.rst 2015-04-04 11:26:41 +0000
247@@ -192,6 +192,18 @@
248 brought countless ills upon
249 the Achaeans.
250
251+A table
252+-------
253+
254++------------------+--------------------------------+------------------------+
255+| H1 | H2 h2 | H3 |
256++==================+================================+========================+
257+| Column A1 | Column A2 | a |
258++------------------+--------------------------------+------------------------+
259+| a very long cell | **bold text** | b |
260++------------------+--------------------------------+------------------------+
261+| hyperlinks | `wp?wiki <interwiki:wp?wiki>`_ | `Xorg <http://x.org>`_ |
262++------------------+--------------------------------+------------------------+
263
264 ====
265 This is not a header
266
267=== modified file 'tests/data/formats/export.tex'
268--- tests/data/formats/export.tex 2014-05-04 19:17:46 +0000
269+++ tests/data/formats/export.tex 2015-04-04 11:26:41 +0000
270@@ -301,6 +301,22 @@
271 \end{lstlisting}
272
273
274+\section{A table}
275+
276+
277+
278+\begin{tabular}{ |c|r|l| }
279+\hline
280+ H1 & H2 h2 & H3 \tabularnewline
281+\hline
282+\hline
283+ Column A1 & Column A2 & a \tabularnewline
284+\hline
285+ a very long cell & \textbf{bold text} & b \tabularnewline
286+\hline
287+ hyperlinks & \href{interwiki:wp?wiki}{wp?wiki} & \href{http://x.org}{Xorg} \tabularnewline
288+\hline
289+\end{tabular}
290
291
292 ====
293
294=== modified file 'tests/data/formats/parsetree.xml'
295--- tests/data/formats/parsetree.xml 2012-11-06 18:20:37 +0000
296+++ tests/data/formats/parsetree.xml 2015-04-04 11:26:41 +0000
297@@ -125,7 +125,9 @@
298 brought countless ills upon
299 the Achaeans.
300 </object>
301+<h level="2">A table</h>
302
303+<table aligns="center,right,left" wraps="1,0,1"><thead><th>H1</th><th>H2 h2</th><th>H3</th></thead><trow><td>Column A1</td><td>Column A2</td><td>a</td></trow><trow><td>a very long cell</td><td><strong>bold text</strong></td><td>b</td></trow><trow><td>hyperlinks</td><td><link href="wp?wiki">wp?wiki</link></td><td><link href="http://x.org">Xorg</link></td></trow></table>
304 <p>====
305 This is not a header
306 </p>
307
308=== modified file 'tests/data/formats/plain.txt'
309--- tests/data/formats/plain.txt 2012-11-06 18:20:37 +0000
310+++ tests/data/formats/plain.txt 2015-04-04 11:26:41 +0000
311@@ -155,6 +155,18 @@
312 brought countless ills upon
313 the Achaeans.
314
315+A table
316+-------
317+
318++------------------+-----------+------+
319+| H1 | H2 h2 | H3 |
320++==================+===========+======+
321+| Column A1 | Column A2 | a |
322++------------------+-----------+------+
323+| a very long cell | bold text | b |
324++------------------+-----------+------+
325+| hyperlinks | wp?wiki | Xorg |
326++------------------+-----------+------+
327
328 ====
329 This is not a header
330
331=== modified file 'tests/data/formats/wiki.txt'
332--- tests/data/formats/wiki.txt 2014-02-02 21:21:24 +0000
333+++ tests/data/formats/wiki.txt 2015-04-04 11:26:41 +0000
334@@ -161,6 +161,13 @@
335 the Achaeans.
336 }}}
337
338+===== A table =====
339+
340+| H1 <| H2 h2 | H3 <|
341+|:----------------:|--------------:|:-----------------------|
342+| Column A1 | Column A2 | a |
343+| a very long cell | **bold text** | b |
344+| hyperlinks | [[wp?wiki]] | [[http://x.org\|Xorg]] |
345
346 ====
347 This is not a header
348
349=== modified file 'tests/objectmanager.py'
350--- tests/objectmanager.py 2014-11-09 11:07:20 +0000
351+++ tests/objectmanager.py 2015-04-04 11:26:41 +0000
352@@ -48,7 +48,7 @@
353 from zim.plugins.sourceview import SourceViewPlugin
354 self.assertEqual(
355 manager.find_plugin('code'),
356- ('sourceview', 'Source View', True, SourceViewPlugin)
357+ ('sourceview', 'Source View', True, SourceViewPlugin, None)
358 )
359
360
361
362=== added file 'tests/tableeditor.py'
363--- tests/tableeditor.py 1970-01-01 00:00:00 +0000
364+++ tests/tableeditor.py 2015-04-04 11:26:41 +0000
365@@ -0,0 +1,84 @@
366+# -*- coding: utf-8 -*-
367+
368+from __future__ import with_statement
369+
370+import tests
371+
372+from tests.pageview import setUpPageView
373+
374+from zim.config import ConfigDict
375+from zim.formats import ParseTree, StubLinker
376+from zim.formats.html import Dumper as HtmlDumper
377+
378+from zim.plugins.tableeditor import *
379+
380+class TestMainWindowExtension(tests.TestCase):
381+
382+ def runTest(self):
383+ window = tests.MockObject()
384+ window.pageview = setUpPageView()
385+ window.ui = tests.MockObject()
386+ window.ui.uimanager = tests.MockObject()
387+ window.ui.uistate = ConfigDict()
388+ window.ui.mainwindow = window # XXX
389+
390+ plugin = TableEditorPlugin()
391+ extension = MainWindowExtension(plugin, window)
392+
393+ with tests.DialogContext(self.checkInsertTableDialog):
394+ extension.insert_table()
395+
396+ tree = window.pageview.get_parsetree()
397+ #~ print tree.tostring()
398+ obj = tree.find('table')
399+
400+ self.assertTrue(obj.attrib['aligns'] == 'left')
401+ self.assertTrue(obj.attrib['wraps'] == '0')
402+
403+ # Parses tree to a table object
404+ tabledata = tree.tostring().replace("<?xml version='1.0' encoding='utf-8'?>", '')\
405+ .replace('<zim-tree>', '').replace('</zim-tree>', '')\
406+ .replace('<td> </td>', '<td>text</td>')
407+
408+ table = plugin.create_table({'type': 'table'}, ElementTree.fromstring(tabledata))
409+
410+ self.assertTrue(isinstance(table, TableViewObject))
411+
412+ def checkInsertTableDialog(self, dialog):
413+ self.assertIsInstance(dialog, EditTableDialog)
414+ dialog.assert_response_ok()
415+
416+class TestEditTableExtension(tests.TestCase):
417+ def checkUpdateTableDialog(self, dialog):
418+ self.assertIsInstance(dialog, EditTableDialog)
419+ dialog.assert_response_ok()
420+
421+ def testChangeTable(self):
422+ window = tests.MockObject()
423+ window.pageview = setUpPageView()
424+ window.ui = tests.MockObject()
425+ window.ui.uimanager = tests.MockObject()
426+ window.ui.uistate = ConfigDict()
427+ window.ui.mainwindow = window # XXX
428+ plugin = TableEditorPlugin()
429+ extension = MainWindowExtension(plugin, window)
430+ obj = plugin.create_table({'aligns': 'normal,normal', 'wraps': '0,0'}, (('h1', 'h2'),('t1', 't2')))
431+ obj.get_widget()
432+
433+ with tests.DialogContext(self.checkUpdateTableDialog):
434+ extension.do_edit_object(obj)
435+
436+ self.assertTrue(isinstance(obj.get_widget().treeview, gtk.TreeView))
437+
438+class TestTableFunctions(tests.TestCase):
439+ def testCellFormater(self):
440+ self.assertEqual(CellFormatReplacer.input_to_cell('**hello**', with_pango=True), '<b>hello</b>')
441+ self.assertEqual(CellFormatReplacer.cell_to_input('<span background="yellow">highlight</span>', with_pango=True),
442+ '__highlight__')
443+ self.assertEqual(CellFormatReplacer.zim_to_cell('<link href="./alink">hello</link>'),
444+ '<span foreground="blue">hello<span size="0">./alink</span></span>')
445+ self.assertEqual(CellFormatReplacer.cell_to_zim('<tt>code-block</tt>'), '<code>code-block</code>')
446+
447+class TestColumnSorting(tests.TestCase):
448+ def testSorting(self):
449+ pass
450\ No newline at end of file
451
452=== modified file 'translations/zim.pot'
453--- translations/zim.pot 2014-09-04 19:38:24 +0000
454+++ translations/zim.pot 2015-04-04 11:26:41 +0000
455@@ -8,7 +8,7 @@
456 msgstr ""
457 "Project-Id-Version: PACKAGE VERSION\n"
458 "Report-Msgid-Bugs-To: \n"
459-"POT-Creation-Date: 2014-09-04 21:38+0200\n"
460+"POT-Creation-Date: 2015-04-04 12:31+0200\n"
461 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
462 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
463 "Language-Team: LANGUAGE <LL@li.org>\n"
464@@ -56,7 +56,7 @@
465
466 #. message for FileNotFoundError |
467 #. Error message in template lookup
468-#: zim/fs.py:476 zim/templates/__init__.py:126
469+#: zim/fs.py:476 zim/templates/__init__.py:125
470 #, python-format
471 msgid "No such file: %s"
472 msgstr ""
473@@ -146,10 +146,10 @@
474 #. label for properties dialog |
475 #. label for file name
476 #: zim/gui/applications.py:804 zim/gui/customtools.py:157
477-#: zim/gui/__init__.py:3352 zim/gui/notebookdialog.py:413
478-#: zim/gui/pageview.py:6857 zim/gui/preferencesdialog.py:244
479-#: zim/gui/templateeditordialog.py:157 zim/notebook.py:838
480-#: zim/plugins/attachmentbrowser.py:726
481+#: zim/gui/__init__.py:3378 zim/gui/notebookdialog.py:413
482+#: zim/gui/pageview.py:7042 zim/gui/preferencesdialog.py:247
483+#: zim/gui/templateeditordialog.py:158 zim/notebook.py:838
484+#: zim/plugins/attachmentbrowser.py:727
485 msgid "Name"
486 msgstr ""
487
488@@ -189,7 +189,7 @@
489
490 #. Input in "Edit Custom Tool" dialog |
491 #. Heading in plugins tab of preferences dialog
492-#: zim/gui/customtools.py:158 zim/gui/preferencesdialog.py:246
493+#: zim/gui/customtools.py:158 zim/gui/preferencesdialog.py:249
494 msgid "Description"
495 msgstr ""
496
497@@ -231,58 +231,58 @@
498 msgstr ""
499
500 #. dialog title
501-#: zim/gui/exportdialog.py:27
502+#: zim/gui/exportdialog.py:31
503 msgid "Export"
504 msgstr ""
505
506 #. Title of progressbar dialog
507-#: zim/gui/exportdialog.py:43 zim/gui/__init__.py:2105
508+#: zim/gui/exportdialog.py:51 zim/gui/__init__.py:2130
509 msgid "Updating index"
510 msgstr ""
511
512 #. Title for progressbar window
513-#: zim/gui/exportdialog.py:51
514+#: zim/gui/exportdialog.py:60
515 msgid "Exporting notebook"
516 msgstr ""
517
518 #. message heading
519-#: zim/gui/exportdialog.py:136
520+#: zim/gui/exportdialog.py:146
521 #, python-format
522 msgid "Folder exists: %s"
523 msgstr ""
524
525 #. detailed message, answers are Yes and No
526-#: zim/gui/exportdialog.py:137
527+#: zim/gui/exportdialog.py:147
528 msgid ""
529 "Folder already exists and has content, exporting to this folder may "
530 "overwrite existing files. Do you want to continue?"
531 msgstr ""
532
533 #. message heading
534-#: zim/gui/exportdialog.py:150
535+#: zim/gui/exportdialog.py:160
536 msgid "File exists"
537 msgstr ""
538
539 #. detailed message, answers are Yes and No
540-#: zim/gui/exportdialog.py:151
541+#: zim/gui/exportdialog.py:161
542 msgid ""
543 "This file already exists.\n"
544 "Do you want to overwrite it?"
545 msgstr ""
546
547 #. title of step in export dialog
548-#: zim/gui/exportdialog.py:162
549+#: zim/gui/exportdialog.py:172
550 msgid "Select the pages to export"
551 msgstr ""
552
553 #. Option in export dialog to export complete notebook |
554 #. Option in versions dialog to show version for complete notebook
555-#: zim/gui/exportdialog.py:168 zim/plugins/versioncontrol/__init__.py:934
556+#: zim/gui/exportdialog.py:178 zim/plugins/versioncontrol/__init__.py:934
557 msgid "Complete _notebook"
558 msgstr ""
559
560 #. Option in export dialog to export selection
561-#: zim/gui/exportdialog.py:173
562+#: zim/gui/exportdialog.py:183
563 msgid "Single _page"
564 msgstr ""
565
566@@ -291,586 +291,586 @@
567 #. Column header |
568 #. Column header search dialog |
569 #. Column header Task List dialog
570-#: zim/gui/exportdialog.py:176 zim/gui/pageview.py:6754
571-#: zim/gui/recentchangesdialog.py:54 zim/gui/searchdialog.py:150
572-#: zim/plugins/quicknote.py:221 zim/plugins/tasklist.py:943
573+#: zim/gui/exportdialog.py:186 zim/gui/pageview.py:6939
574+#: zim/gui/recentchangesdialog.py:54 zim/gui/searchdialog.py:151
575+#: zim/plugins/quicknote.py:221 zim/plugins/tasklist.py:947
576 msgid "Page"
577 msgstr ""
578
579 #. Input field in export dialog
580-#: zim/gui/exportdialog.py:177
581+#: zim/gui/exportdialog.py:187
582 msgid "Include subpages"
583 msgstr ""
584
585 #. title of step in export dialog
586-#: zim/gui/exportdialog.py:204
587+#: zim/gui/exportdialog.py:214
588 msgid "Select the export format"
589 msgstr ""
590
591 #. Option in drop down menu to specify another file
592-#: zim/gui/exportdialog.py:206
593+#: zim/gui/exportdialog.py:216
594 msgid "Other..."
595 msgstr ""
596
597 #. Input label in the export dialog |
598 #. label in "insert date" dialog
599-#: zim/gui/exportdialog.py:218 zim/gui/pageview.py:6031
600+#: zim/gui/exportdialog.py:228 zim/gui/pageview.py:6216
601 msgid "Format"
602 msgstr ""
603
604 #. Input label in the export dialog
605-#: zim/gui/exportdialog.py:219
606+#: zim/gui/exportdialog.py:229
607 msgid "Template"
608 msgstr ""
609
610 #. radio option in export dialog
611-#: zim/gui/exportdialog.py:222
612+#: zim/gui/exportdialog.py:232
613 msgid "Link files under document root with full file path"
614 msgstr ""
615
616 #. radio option in export dialog
617-#: zim/gui/exportdialog.py:223
618+#: zim/gui/exportdialog.py:233
619 msgid "Map document root to URL"
620 msgstr ""
621
622 #. label for button with URL
623-#: zim/gui/exportdialog.py:233 zim/gui/templateeditordialog.py:70
624+#: zim/gui/exportdialog.py:244 zim/gui/templateeditordialog.py:71
625 msgid "Get more templates online"
626 msgstr ""
627
628 #. title of step in export dialog
629-#: zim/gui/exportdialog.py:307
630+#: zim/gui/exportdialog.py:318
631 msgid "Select the output file or folder"
632 msgstr ""
633
634 #. Label for option in export dialog
635-#: zim/gui/exportdialog.py:313
636+#: zim/gui/exportdialog.py:324
637 msgid "Export each page to a separate file"
638 msgstr ""
639
640 #. Label for option in export dialog
641-#: zim/gui/exportdialog.py:315
642+#: zim/gui/exportdialog.py:326
643 msgid "Export all pages to a single file"
644 msgstr ""
645
646 #. Label for folder selection in export dialog
647-#: zim/gui/exportdialog.py:318
648+#: zim/gui/exportdialog.py:329
649 msgid "Output folder"
650 msgstr ""
651
652 #. Label for setting a name for the index of exported pages
653-#: zim/gui/exportdialog.py:320
654+#: zim/gui/exportdialog.py:331
655 msgid "Index page"
656 msgstr ""
657
658 #. Label for file selection in export dialog
659-#: zim/gui/exportdialog.py:323
660+#: zim/gui/exportdialog.py:334
661 msgid "Output file"
662 msgstr ""
663
664 #. label in export dialog
665-#: zim/gui/exportdialog.py:412
666+#: zim/gui/exportdialog.py:423
667 #, python-format
668-msgid "%(n_errors)i errors and %(n_warnings)i warnings occurred, see log"
669+msgid "%(n_error)i errors and %(n_warning)i warnings occurred, see log"
670 msgstr ""
671
672 #. label in export dialog
673-#: zim/gui/exportdialog.py:415
674+#: zim/gui/exportdialog.py:426
675 #, python-format
676 msgid "%i errors occurred, see log"
677 msgstr ""
678
679 #. label in export dialog
680-#: zim/gui/exportdialog.py:418
681+#: zim/gui/exportdialog.py:429
682 #, python-format
683 msgid "%i warnings occurred, see log"
684 msgstr ""
685
686 #. label in export dialog
687-#: zim/gui/exportdialog.py:423
688+#: zim/gui/exportdialog.py:434
689 msgid "Export completed"
690 msgstr ""
691
692 #. button in export dialog |
693 #. button in e.g. equation editor dialog
694-#: zim/gui/exportdialog.py:426 zim/plugins/base/imagegenerator.py:286
695+#: zim/gui/exportdialog.py:437 zim/plugins/base/imagegenerator.py:286
696 msgid "View _Log"
697 msgstr ""
698
699 #. Menu title
700-#: zim/gui/__init__.py:61
701+#: zim/gui/__init__.py:63
702 msgid "_File"
703 msgstr ""
704
705 #. Menu title
706-#: zim/gui/__init__.py:62
707+#: zim/gui/__init__.py:64
708 msgid "_Edit"
709 msgstr ""
710
711 #. Menu title |
712 #. button label
713-#: zim/gui/__init__.py:63 zim/gui/templateeditordialog.py:42
714+#: zim/gui/__init__.py:65 zim/gui/templateeditordialog.py:42
715 msgid "_View"
716 msgstr ""
717
718 #. Menu title |
719 #. Button label
720-#: zim/gui/__init__.py:64 zim/gui/pageview.py:6021
721+#: zim/gui/__init__.py:66 zim/gui/pageview.py:6206
722 #: zim/plugins/insertsymbol.py:138
723 msgid "_Insert"
724 msgstr ""
725
726 #. Menu title
727-#: zim/gui/__init__.py:65
728+#: zim/gui/__init__.py:67
729 msgid "_Search"
730 msgstr ""
731
732 #. Menu title
733-#: zim/gui/__init__.py:66
734+#: zim/gui/__init__.py:68
735 msgid "For_mat"
736 msgstr ""
737
738 #. Menu title
739-#: zim/gui/__init__.py:67
740+#: zim/gui/__init__.py:69
741 msgid "_Tools"
742 msgstr ""
743
744 #. Menu title
745-#: zim/gui/__init__.py:68
746+#: zim/gui/__init__.py:70
747 msgid "_Go"
748 msgstr ""
749
750 #. Menu title
751-#: zim/gui/__init__.py:69
752+#: zim/gui/__init__.py:71
753 msgid "_Help"
754 msgstr ""
755
756 #. Menu title
757-#: zim/gui/__init__.py:70
758+#: zim/gui/__init__.py:72
759 msgid "P_athbar"
760 msgstr ""
761
762 #. Menu title |
763 #. Menu item
764-#: zim/gui/__init__.py:71 zim/gui/__init__.py:141 zim/gui/__init__.py:151
765+#: zim/gui/__init__.py:73 zim/gui/__init__.py:143 zim/gui/__init__.py:153
766 msgid "_Toolbar"
767 msgstr ""
768
769 #. Menu item
770-#: zim/gui/__init__.py:74
771+#: zim/gui/__init__.py:76
772 msgid "_New Page..."
773 msgstr ""
774
775 #. Menu item
776-#: zim/gui/__init__.py:75
777+#: zim/gui/__init__.py:77
778 msgid "New S_ub Page..."
779 msgstr ""
780
781 #. Menu item
782-#: zim/gui/__init__.py:76
783+#: zim/gui/__init__.py:78
784 msgid "_Open Another Notebook..."
785 msgstr ""
786
787 #. Menu item |
788 #. menu item to open a link
789-#: zim/gui/__init__.py:77 zim/gui/pageview.py:5405
790+#: zim/gui/__init__.py:79 zim/gui/pageview.py:5558
791 #: zim/plugins/backlinkpane.py:120
792 msgid "Open in New _Window"
793 msgstr ""
794
795 #. Menu item
796-#: zim/gui/__init__.py:78
797+#: zim/gui/__init__.py:80
798 msgid "_Import Page..."
799 msgstr ""
800
801 #. Menu item
802-#: zim/gui/__init__.py:79
803+#: zim/gui/__init__.py:81
804 msgid "_Save"
805 msgstr ""
806
807 #. Menu item
808-#: zim/gui/__init__.py:80
809+#: zim/gui/__init__.py:82
810 msgid "Save A _Copy..."
811 msgstr ""
812
813 #. Menu item
814-#: zim/gui/__init__.py:81
815+#: zim/gui/__init__.py:83
816 msgid "E_xport..."
817 msgstr ""
818
819 #. Menu item
820-#: zim/gui/__init__.py:82
821+#: zim/gui/__init__.py:84
822 msgid "_Send To..."
823 msgstr ""
824
825 #. Menu item
826-#: zim/gui/__init__.py:83
827+#: zim/gui/__init__.py:85
828 msgid "_Move Page..."
829 msgstr ""
830
831 #. Menu item
832-#: zim/gui/__init__.py:84
833+#: zim/gui/__init__.py:86
834 msgid "_Rename Page..."
835 msgstr ""
836
837 #. Menu item
838-#: zim/gui/__init__.py:85
839+#: zim/gui/__init__.py:87
840 msgid "_Delete Page"
841 msgstr ""
842
843 #. Menu item
844-#: zim/gui/__init__.py:86
845+#: zim/gui/__init__.py:88
846 msgid "Proper_ties"
847 msgstr ""
848
849 #. Menu item
850-#: zim/gui/__init__.py:87
851+#: zim/gui/__init__.py:89
852 msgid "_Close"
853 msgstr ""
854
855 #. Menu item |
856 #. menu item in tray icon menu
857-#: zim/gui/__init__.py:88 zim/plugins/trayicon.py:126
858+#: zim/gui/__init__.py:90 zim/plugins/trayicon.py:126
859 msgid "_Quit"
860 msgstr ""
861
862 #. Menu item
863-#: zim/gui/__init__.py:89
864+#: zim/gui/__init__.py:91
865 msgid "_Search..."
866 msgstr ""
867
868 #. Menu item
869-#: zim/gui/__init__.py:90
870+#: zim/gui/__init__.py:92
871 msgid "Search _Backlinks..."
872 msgstr ""
873
874 #. Menu item
875-#: zim/gui/__init__.py:91
876+#: zim/gui/__init__.py:93
877 msgid "Recent Changes..."
878 msgstr ""
879
880 #. Menu item
881-#: zim/gui/__init__.py:92
882+#: zim/gui/__init__.py:94
883 msgid "Copy _Location"
884 msgstr ""
885
886 #. Menu item
887-#: zim/gui/__init__.py:93
888+#: zim/gui/__init__.py:95
889 msgid "_Templates"
890 msgstr ""
891
892 #. Menu item
893-#: zim/gui/__init__.py:94
894+#: zim/gui/__init__.py:96
895 msgid "Pr_eferences"
896 msgstr ""
897
898 #. Menu item
899-#: zim/gui/__init__.py:95
900+#: zim/gui/__init__.py:97
901 msgid "_Reload"
902 msgstr ""
903
904 #. Menu item
905-#: zim/gui/__init__.py:96
906+#: zim/gui/__init__.py:98
907 msgid "Open Attachments _Folder"
908 msgstr ""
909
910 #. Menu item
911-#: zim/gui/__init__.py:97
912+#: zim/gui/__init__.py:99
913 msgid "Open _Notebook Folder"
914 msgstr ""
915
916 #. Menu item
917-#: zim/gui/__init__.py:98
918+#: zim/gui/__init__.py:100
919 msgid "Open _Document Root"
920 msgstr ""
921
922 #. Menu item
923-#: zim/gui/__init__.py:99
924+#: zim/gui/__init__.py:101
925 msgid "Open _Document Folder"
926 msgstr ""
927
928 #. Menu item
929-#: zim/gui/__init__.py:100
930+#: zim/gui/__init__.py:102
931 msgid "Attach _File"
932 msgstr ""
933
934 #. Menu item
935-#: zim/gui/__init__.py:100
936+#: zim/gui/__init__.py:102
937 msgid "Attach external file"
938 msgstr ""
939
940 #. Menu item
941-#: zim/gui/__init__.py:101
942+#: zim/gui/__init__.py:103
943 msgid "Edit _Source"
944 msgstr ""
945
946 #. Menu item
947-#: zim/gui/__init__.py:102
948+#: zim/gui/__init__.py:104
949 msgid "Start _Web Server"
950 msgstr ""
951
952 #. Menu item
953-#: zim/gui/__init__.py:103
954+#: zim/gui/__init__.py:105
955 msgid "Update Index"
956 msgstr ""
957
958 #. Menu item
959-#: zim/gui/__init__.py:104
960+#: zim/gui/__init__.py:106
961 msgid "Custom _Tools"
962 msgstr ""
963
964 #. Menu item
965-#: zim/gui/__init__.py:105
966+#: zim/gui/__init__.py:107
967 msgid "_Back"
968 msgstr ""
969
970 #. Menu item
971-#: zim/gui/__init__.py:105
972+#: zim/gui/__init__.py:107
973 msgid "Go page back"
974 msgstr ""
975
976 #. Menu item
977-#: zim/gui/__init__.py:106
978+#: zim/gui/__init__.py:108
979 msgid "_Forward"
980 msgstr ""
981
982 #. Menu item
983-#: zim/gui/__init__.py:106
984+#: zim/gui/__init__.py:108
985 msgid "Go page forward"
986 msgstr ""
987
988 #. Menu item
989-#: zim/gui/__init__.py:107
990+#: zim/gui/__init__.py:109
991 msgid "_Parent"
992 msgstr ""
993
994 #. Menu item
995-#: zim/gui/__init__.py:107
996+#: zim/gui/__init__.py:109
997 msgid "Go to parent page"
998 msgstr ""
999
1000 #. Menu item
1001-#: zim/gui/__init__.py:108
1002+#: zim/gui/__init__.py:110
1003 msgid "_Child"
1004 msgstr ""
1005
1006 #. Menu item
1007-#: zim/gui/__init__.py:108
1008+#: zim/gui/__init__.py:110
1009 msgid "Go to child page"
1010 msgstr ""
1011
1012 #. Menu item
1013-#: zim/gui/__init__.py:109
1014+#: zim/gui/__init__.py:111
1015 msgid "_Previous in index"
1016 msgstr ""
1017
1018 #. Menu item
1019-#: zim/gui/__init__.py:109
1020+#: zim/gui/__init__.py:111
1021 msgid "Go to previous page"
1022 msgstr ""
1023
1024 #. Menu item
1025-#: zim/gui/__init__.py:110
1026+#: zim/gui/__init__.py:112
1027 msgid "_Next in index"
1028 msgstr ""
1029
1030 #. Menu item
1031-#: zim/gui/__init__.py:110
1032+#: zim/gui/__init__.py:112
1033 msgid "Go to next page"
1034 msgstr ""
1035
1036 #. Menu item
1037-#: zim/gui/__init__.py:111
1038+#: zim/gui/__init__.py:113
1039 msgid "_Home"
1040 msgstr ""
1041
1042 #. Menu item
1043-#: zim/gui/__init__.py:111
1044+#: zim/gui/__init__.py:113
1045 msgid "Go home"
1046 msgstr ""
1047
1048 #. Menu item
1049-#: zim/gui/__init__.py:112
1050+#: zim/gui/__init__.py:114
1051 msgid "_Jump To..."
1052 msgstr ""
1053
1054 #. Menu item
1055-#: zim/gui/__init__.py:113
1056+#: zim/gui/__init__.py:115
1057 msgid "_Contents"
1058 msgstr ""
1059
1060 #. Menu item
1061-#: zim/gui/__init__.py:114
1062+#: zim/gui/__init__.py:116
1063 msgid "_FAQ"
1064 msgstr ""
1065
1066 #. Menu item
1067-#: zim/gui/__init__.py:115
1068+#: zim/gui/__init__.py:117
1069 msgid "_Keybindings"
1070 msgstr ""
1071
1072 #. Menu item
1073-#: zim/gui/__init__.py:116
1074+#: zim/gui/__init__.py:118
1075 msgid "_Bugs"
1076 msgstr ""
1077
1078 #. Menu item
1079-#: zim/gui/__init__.py:117
1080+#: zim/gui/__init__.py:119
1081 msgid "_About"
1082 msgstr ""
1083
1084 #. Menu item
1085-#: zim/gui/__init__.py:135
1086+#: zim/gui/__init__.py:137
1087 msgid "_All Panes"
1088 msgstr ""
1089
1090 #. Menu item
1091-#: zim/gui/__init__.py:135
1092+#: zim/gui/__init__.py:137
1093 msgid "Show All Panes"
1094 msgstr ""
1095
1096 #. Menu item
1097-#: zim/gui/__init__.py:142 zim/gui/__init__.py:152
1098+#: zim/gui/__init__.py:144 zim/gui/__init__.py:154
1099 msgid "_Statusbar"
1100 msgstr ""
1101
1102 #. Menu item # FIXME review text
1103-#: zim/gui/__init__.py:143 zim/gui/__init__.py:153
1104+#: zim/gui/__init__.py:145 zim/gui/__init__.py:155
1105 msgid "_Side Panes"
1106 msgstr ""
1107
1108 #. Menu item # FIXME review text
1109-#: zim/gui/__init__.py:143 zim/gui/__init__.py:153
1110+#: zim/gui/__init__.py:145 zim/gui/__init__.py:155
1111 msgid "Show Side Panes"
1112 msgstr ""
1113
1114 #. Menu item
1115-#: zim/gui/__init__.py:144 zim/gui/__init__.py:154
1116+#: zim/gui/__init__.py:146 zim/gui/__init__.py:156
1117 msgid "_Fullscreen"
1118 msgstr ""
1119
1120 #. menu item
1121-#: zim/gui/__init__.py:145 zim/gui/__init__.py:155
1122+#: zim/gui/__init__.py:147 zim/gui/__init__.py:157
1123 msgid "Notebook _Editable"
1124 msgstr ""
1125
1126 #. menu item
1127-#: zim/gui/__init__.py:145 zim/gui/__init__.py:155
1128+#: zim/gui/__init__.py:147 zim/gui/__init__.py:157
1129 msgid "Toggle notebook editable"
1130 msgstr ""
1131
1132 #. Menu item
1133-#: zim/gui/__init__.py:161
1134+#: zim/gui/__init__.py:163
1135 msgid "_None"
1136 msgstr ""
1137
1138 #. Menu item
1139-#: zim/gui/__init__.py:162
1140+#: zim/gui/__init__.py:164
1141 msgid "_Recent pages"
1142 msgstr ""
1143
1144 #. Menu item
1145-#: zim/gui/__init__.py:163
1146+#: zim/gui/__init__.py:165
1147 msgid "Recently _Changed pages"
1148 msgstr ""
1149
1150 #. Menu item
1151-#: zim/gui/__init__.py:164
1152+#: zim/gui/__init__.py:166
1153 msgid "_History"
1154 msgstr ""
1155
1156 #. Menu item
1157-#: zim/gui/__init__.py:165
1158+#: zim/gui/__init__.py:167
1159 msgid "_Page Hierarchy"
1160 msgstr ""
1161
1162 #. Menu item
1163-#: zim/gui/__init__.py:178
1164+#: zim/gui/__init__.py:180
1165 msgid "Icons _And Text"
1166 msgstr ""
1167
1168 #. Menu item
1169-#: zim/gui/__init__.py:179
1170+#: zim/gui/__init__.py:181
1171 msgid "_Icons Only"
1172 msgstr ""
1173
1174 #. Menu item
1175-#: zim/gui/__init__.py:180
1176+#: zim/gui/__init__.py:182
1177 msgid "_Text Only"
1178 msgstr ""
1179
1180 #. Menu item
1181-#: zim/gui/__init__.py:186
1182+#: zim/gui/__init__.py:188
1183 msgid "_Large Icons"
1184 msgstr ""
1185
1186 #. Menu item
1187-#: zim/gui/__init__.py:187
1188+#: zim/gui/__init__.py:189
1189 msgid "_Small Icons"
1190 msgstr ""
1191
1192 #. Menu item
1193-#: zim/gui/__init__.py:188
1194+#: zim/gui/__init__.py:190
1195 msgid "_Tiny Icons"
1196 msgstr ""
1197
1198 #. Option in the preferences dialog
1199-#: zim/gui/__init__.py:202
1200+#: zim/gui/__init__.py:204
1201 msgid "Add 'tearoff' strips to the menus"
1202 msgstr ""
1203
1204 #. Option in the preferences dialog
1205-#: zim/gui/__init__.py:204
1206+#: zim/gui/__init__.py:206
1207 msgid "Use <Ctrl><Space> to switch to the side pane"
1208 msgstr ""
1209
1210 #. Option in the preferences dialog
1211-#: zim/gui/__init__.py:208
1212+#: zim/gui/__init__.py:210
1213 msgid "Remove links when deleting pages"
1214 msgstr ""
1215
1216 #. Option in the preferences dialog
1217-#: zim/gui/__init__.py:210
1218+#: zim/gui/__init__.py:212
1219 msgid "Always use last cursor position when opening a page"
1220 msgstr ""
1221
1222 #. Error description for "no such file or folder"
1223-#: zim/gui/__init__.py:272
1224+#: zim/gui/__init__.py:274
1225 msgid ""
1226 "The file or folder you specified does not exist.\n"
1227 "Please check if you the path is correct."
1228 msgstr ""
1229
1230 #. Error message, %s will be the file path
1231-#: zim/gui/__init__.py:279
1232+#: zim/gui/__init__.py:281
1233 #, python-format
1234 msgid "No such file or folder: %s"
1235 msgstr ""
1236
1237 #. Error description
1238-#: zim/gui/__init__.py:291
1239+#: zim/gui/__init__.py:293
1240 msgid "Page has un-saved changes"
1241 msgstr ""
1242
1243 #. Dialog title |
1244 #. input label
1245-#: zim/gui/__init__.py:594 zim/gui/searchdialog.py:24
1246+#: zim/gui/__init__.py:596 zim/gui/searchdialog.py:24
1247 #: zim/gui/searchdialog.py:31
1248 msgid "Search"
1249 msgstr ""
1250
1251 #. label in search entry
1252-#: zim/gui/__init__.py:598
1253+#: zim/gui/__init__.py:601
1254 msgid "Search Pages..."
1255 msgstr ""
1256
1257 #. Short question for question prompt
1258-#: zim/gui/__init__.py:665
1259+#: zim/gui/__init__.py:676
1260 msgid "Upgrade Notebook?"
1261 msgstr ""
1262
1263 #. Explanation for question to upgrade notebook
1264-#: zim/gui/__init__.py:666
1265+#: zim/gui/__init__.py:677
1266 msgid ""
1267 "This notebook was created by an older of version of zim.\n"
1268 "Do you want to upgrade it to the latest version now?\n"
1269@@ -884,12 +884,12 @@
1270 msgstr ""
1271
1272 #. Title of progressbar dialog
1273-#: zim/gui/__init__.py:678
1274+#: zim/gui/__init__.py:689
1275 msgid "Upgrading notebook"
1276 msgstr ""
1277
1278 #. question dialog text
1279-#: zim/gui/__init__.py:1676
1280+#: zim/gui/__init__.py:1701
1281 msgid ""
1282 "The index is still busy updating. Until this is finished links can not be "
1283 "updated correctly. Performing this action now could break links, do you want "
1284@@ -897,22 +897,22 @@
1285 msgstr ""
1286
1287 #. Title of progressbar dialog
1288-#: zim/gui/__init__.py:1687
1289+#: zim/gui/__init__.py:1712
1290 msgid "Updating Links"
1291 msgstr ""
1292
1293 #. Title of progressbar dialog
1294-#: zim/gui/__init__.py:1714 zim/gui/__init__.py:3451
1295+#: zim/gui/__init__.py:1739 zim/gui/__init__.py:3477
1296 msgid "Removing Links"
1297 msgstr ""
1298
1299 #. Heading in a question dialog for creating a folder
1300-#: zim/gui/__init__.py:1822 zim/gui/pageview.py:5715
1301+#: zim/gui/__init__.py:1847 zim/gui/pageview.py:5871
1302 msgid "Create folder?"
1303 msgstr ""
1304
1305 #. Text in a question dialog for creating a folder, %s will be the folder base name
1306-#: zim/gui/__init__.py:1824
1307+#: zim/gui/__init__.py:1849
1308 #, python-format
1309 msgid ""
1310 "The folder \"%s\" does not yet exist.\n"
1311@@ -920,51 +920,51 @@
1312 msgstr ""
1313
1314 #. error when external application fails
1315-#: zim/gui/__init__.py:1944 zim/gui/__init__.py:2062
1316+#: zim/gui/__init__.py:1969 zim/gui/__init__.py:2087
1317 #, python-format
1318 msgid "Could not open: %s"
1319 msgstr ""
1320
1321 #. Error message
1322-#: zim/gui/__init__.py:1959
1323+#: zim/gui/__init__.py:1984
1324 msgid "This page does not have an attachments folder"
1325 msgstr ""
1326
1327 #. main text for dialog for editing external files
1328-#: zim/gui/__init__.py:2053
1329+#: zim/gui/__init__.py:2078
1330 #, python-format
1331 msgid "Editing file: %s"
1332 msgstr ""
1333
1334 #. description for dialog for editing external files
1335-#: zim/gui/__init__.py:2055
1336+#: zim/gui/__init__.py:2080
1337 msgid ""
1338 "You are editing a file in an external application. You can close this dialog "
1339 "when you are done"
1340 msgstr ""
1341
1342 #. General description of zim itself
1343-#: zim/gui/__init__.py:2259
1344+#: zim/gui/__init__.py:2284
1345 msgid "A desktop wiki"
1346 msgstr ""
1347
1348 #. This string needs to be translated with names of the translators for this language
1349-#: zim/gui/__init__.py:2267
1350+#: zim/gui/__init__.py:2292
1351 msgid "translator-credits"
1352 msgstr ""
1353
1354 #. Label for pageindex tab
1355-#: zim/gui/__init__.py:2365
1356+#: zim/gui/__init__.py:2390
1357 msgid "Index"
1358 msgstr ""
1359
1360 #. page status in statusbar
1361-#: zim/gui/__init__.py:2440
1362+#: zim/gui/__init__.py:2465
1363 msgid "readonly"
1364 msgstr ""
1365
1366 #. Label for button with backlinks in statusbar
1367-#: zim/gui/__init__.py:2962
1368+#: zim/gui/__init__.py:2987
1369 #, python-format
1370 msgid "%i _Backlink..."
1371 msgid_plural "%i _Backlinks..."
1372@@ -972,13 +972,13 @@
1373 msgstr[1] ""
1374
1375 #. Heading of error dialog
1376-#: zim/gui/__init__.py:3073
1377+#: zim/gui/__init__.py:3098
1378 #, python-format
1379 msgid "Could not save page: %s"
1380 msgstr ""
1381
1382 #. text in error dialog when saving page failed
1383-#: zim/gui/__init__.py:3077
1384+#: zim/gui/__init__.py:3102
1385 msgid ""
1386 "To continue you can save a copy of this page or discard\n"
1387 "any changes. If you save a copy changes will be also\n"
1388@@ -986,85 +986,85 @@
1389 msgstr ""
1390
1391 #. Button in error dialog
1392-#: zim/gui/__init__.py:3108
1393+#: zim/gui/__init__.py:3133
1394 msgid "_Discard Changes"
1395 msgstr ""
1396
1397 #. Button in error dialog
1398-#: zim/gui/__init__.py:3113
1399+#: zim/gui/__init__.py:3138
1400 msgid "_Save Copy"
1401 msgstr ""
1402
1403 #. Dialog title
1404-#: zim/gui/__init__.py:3150
1405+#: zim/gui/__init__.py:3176
1406 msgid "Jump to"
1407 msgstr ""
1408
1409 #. Label for page input
1410-#: zim/gui/__init__.py:3155
1411+#: zim/gui/__init__.py:3181
1412 msgid "Jump to Page"
1413 msgstr ""
1414
1415 #. Dialog title
1416-#: zim/gui/__init__.py:3174
1417+#: zim/gui/__init__.py:3200
1418 msgid "New Sub Page"
1419 msgstr ""
1420
1421 #. Dialog title
1422-#: zim/gui/__init__.py:3175
1423+#: zim/gui/__init__.py:3201
1424 msgid "New Page"
1425 msgstr ""
1426
1427 #. Dialog text in 'new page' dialog
1428-#: zim/gui/__init__.py:3179
1429+#: zim/gui/__init__.py:3205
1430 msgid ""
1431 "Please note that linking to a non-existing page\n"
1432 "also creates a new page automatically."
1433 msgstr ""
1434
1435 #. Input label
1436-#: zim/gui/__init__.py:3194
1437+#: zim/gui/__init__.py:3220
1438 msgid "Page Name"
1439 msgstr ""
1440
1441 #. Choice label
1442-#: zim/gui/__init__.py:3195
1443+#: zim/gui/__init__.py:3221
1444 msgid "Page Template"
1445 msgstr ""
1446
1447 #. Error when creating new page
1448-#: zim/gui/__init__.py:3213
1449+#: zim/gui/__init__.py:3239
1450 msgid "Page exists"
1451 msgstr ""
1452
1453 #. Dialog title of file save dialog
1454-#: zim/gui/__init__.py:3228
1455+#: zim/gui/__init__.py:3254
1456 msgid "Save Copy"
1457 msgstr ""
1458
1459 #. Dialog title
1460-#: zim/gui/__init__.py:3252
1461+#: zim/gui/__init__.py:3278
1462 msgid "Import Page"
1463 msgstr ""
1464
1465 #. File filter for '*.txt'
1466-#: zim/gui/__init__.py:3253
1467+#: zim/gui/__init__.py:3279
1468 msgid "Text Files"
1469 msgstr ""
1470
1471 #. Dialog title
1472-#: zim/gui/__init__.py:3281
1473+#: zim/gui/__init__.py:3307
1474 msgid "Move Page"
1475 msgstr ""
1476
1477 #. Heading in 'move page' dialog - %s is the page name
1478-#: zim/gui/__init__.py:3286
1479+#: zim/gui/__init__.py:3312
1480 #, python-format
1481 msgid "Move page \"%s\""
1482 msgstr ""
1483
1484 #. label in MovePage dialog - %i is number of backlinks
1485-#: zim/gui/__init__.py:3297 zim/gui/__init__.py:3346
1486+#: zim/gui/__init__.py:3323 zim/gui/__init__.py:3372
1487 #, python-format
1488 msgid "Update %i page linking to this page"
1489 msgid_plural "Update %i pages linking to this page"
1490@@ -1073,39 +1073,39 @@
1491
1492 #. Input label for the section to move a page to |
1493 #. input label
1494-#: zim/gui/__init__.py:3302 zim/plugins/calendar.py:101
1495+#: zim/gui/__init__.py:3328 zim/plugins/calendar.py:101
1496 msgid "Section"
1497 msgstr ""
1498
1499 #. Dialog title
1500-#: zim/gui/__init__.py:3331
1501+#: zim/gui/__init__.py:3357
1502 msgid "Rename Page"
1503 msgstr ""
1504
1505 #. label in 'rename page' dialog - %s is the page name
1506-#: zim/gui/__init__.py:3335
1507+#: zim/gui/__init__.py:3361
1508 #, python-format
1509 msgid "Rename page \"%s\""
1510 msgstr ""
1511
1512 #. Option in the 'rename page' dialog
1513-#: zim/gui/__init__.py:3354
1514+#: zim/gui/__init__.py:3380
1515 msgid "Update the heading of this page"
1516 msgstr ""
1517
1518 #. Dialog title
1519-#: zim/gui/__init__.py:3389
1520+#: zim/gui/__init__.py:3415
1521 msgid "Delete Page"
1522 msgstr ""
1523
1524 #. Heading in 'delete page' dialog - %s is the page name
1525-#: zim/gui/__init__.py:3402
1526+#: zim/gui/__init__.py:3428
1527 #, python-format
1528 msgid "Delete page \"%s\"?"
1529 msgstr ""
1530
1531 #. Text in 'delete page' dialog - %s is the page name
1532-#: zim/gui/__init__.py:3404
1533+#: zim/gui/__init__.py:3430
1534 #, python-format
1535 msgid ""
1536 "Page \"%s\" and all of it's\n"
1537@@ -1113,7 +1113,7 @@
1538 msgstr ""
1539
1540 #. label in DeletePage dialog - %i is number of backlinks
1541-#: zim/gui/__init__.py:3417
1542+#: zim/gui/__init__.py:3443
1543 #, python-format
1544 msgid "Remove links from %i page linking to this page"
1545 msgid_plural "Remove links from %i pages linking to this page"
1546@@ -1121,7 +1121,7 @@
1547 msgstr[1] ""
1548
1549 #. label in the DeletePage dialog to warn user of attachments being deleted
1550-#: zim/gui/__init__.py:3436
1551+#: zim/gui/__init__.py:3462
1552 #, python-format
1553 msgid "%i file will be deleted"
1554 msgid_plural "%i files will be deleted"
1555@@ -1129,25 +1129,25 @@
1556 msgstr[1] ""
1557
1558 #. Dialog title
1559-#: zim/gui/__init__.py:3465
1560+#: zim/gui/__init__.py:3491
1561 msgid "Attach File"
1562 msgstr ""
1563
1564 #. Error dialog - %s is the full page name
1565-#: zim/gui/__init__.py:3472
1566+#: zim/gui/__init__.py:3498
1567 #, python-format
1568 msgid "Page \"%s\" does not have a folder for attachments"
1569 msgstr ""
1570
1571 #. checkbox in the "Attach File" dialog
1572-#: zim/gui/__init__.py:3477
1573+#: zim/gui/__init__.py:3503
1574 msgid "Insert images as link"
1575 msgstr ""
1576
1577 #. Column heading in 'open notebook' dialog |
1578 #. Field in web server gui |
1579 #. Field to select Notebook from drop down list
1580-#: zim/gui/notebookdialog.py:175 zim/gui/server.py:97
1581+#: zim/gui/notebookdialog.py:175 zim/gui/server.py:98
1582 #: zim/plugins/quicknote.py:428
1583 msgid "Notebook"
1584 msgstr ""
1585@@ -1177,394 +1177,405 @@
1586 msgid "Folder"
1587 msgstr ""
1588
1589+#. Label for object manager
1590+#: zim/gui/objectmanager.py:144
1591+msgid "No plugin is available to display this object."
1592+msgstr ""
1593+
1594+#. Label for object manager
1595+#: zim/gui/objectmanager.py:151
1596+#, python-format
1597+msgid "Plugin %s is required to display this object."
1598+msgstr ""
1599+
1600 #. statusbar message
1601-#: zim/gui/pageindex.py:758
1602+#: zim/gui/pageindex.py:745
1603 msgid "Updating index..."
1604 msgstr ""
1605
1606 #. Menu item
1607-#: zim/gui/pageview.py:113
1608+#: zim/gui/pageview.py:119
1609 msgid "_Undo"
1610 msgstr ""
1611
1612 #. Menu item
1613-#: zim/gui/pageview.py:114
1614+#: zim/gui/pageview.py:120
1615 msgid "_Redo"
1616 msgstr ""
1617
1618 #. Menu item
1619-#: zim/gui/pageview.py:116
1620+#: zim/gui/pageview.py:122
1621 msgid "Cu_t"
1622 msgstr ""
1623
1624 #. Menu item
1625-#: zim/gui/pageview.py:117
1626+#: zim/gui/pageview.py:123
1627 msgid "_Copy"
1628 msgstr ""
1629
1630 #. Menu item
1631-#: zim/gui/pageview.py:118
1632+#: zim/gui/pageview.py:124
1633 msgid "_Paste"
1634 msgstr ""
1635
1636 #. Menu item
1637-#: zim/gui/pageview.py:119
1638+#: zim/gui/pageview.py:125
1639 msgid "_Delete"
1640 msgstr ""
1641
1642 #. Menu item
1643-#: zim/gui/pageview.py:120
1644+#: zim/gui/pageview.py:126
1645 msgid "Toggle Checkbox 'V'"
1646 msgstr ""
1647
1648 #. Menu item
1649-#: zim/gui/pageview.py:121
1650+#: zim/gui/pageview.py:127
1651 msgid "Toggle Checkbox 'X'"
1652 msgstr ""
1653
1654 #. Menu item
1655-#: zim/gui/pageview.py:122
1656+#: zim/gui/pageview.py:128
1657 msgid "_Edit Link or Object..."
1658 msgstr ""
1659
1660 #. Menu item
1661-#: zim/gui/pageview.py:123 zim/gui/pageview.py:5327
1662+#: zim/gui/pageview.py:129 zim/gui/pageview.py:5480
1663 msgid "_Remove Link"
1664 msgstr ""
1665
1666 #. Menu item
1667-#: zim/gui/pageview.py:124
1668+#: zim/gui/pageview.py:130
1669 msgid "_Date and Time..."
1670 msgstr ""
1671
1672 #. Menu item
1673-#: zim/gui/pageview.py:125
1674+#: zim/gui/pageview.py:131
1675 msgid "_Image..."
1676 msgstr ""
1677
1678 #. Menu item |
1679 #. Menu item,
1680-#: zim/gui/pageview.py:126 zim/gui/pageview.py:129
1681+#: zim/gui/pageview.py:132 zim/gui/pageview.py:135
1682 msgid "Bulle_t List"
1683 msgstr ""
1684
1685 #. Menu item |
1686 #. Menu item,
1687-#: zim/gui/pageview.py:127 zim/gui/pageview.py:130
1688+#: zim/gui/pageview.py:133 zim/gui/pageview.py:136
1689 msgid "_Numbered List"
1690 msgstr ""
1691
1692 #. Menu item,
1693-#: zim/gui/pageview.py:128 zim/gui/pageview.py:131
1694+#: zim/gui/pageview.py:134 zim/gui/pageview.py:137
1695 msgid "Checkbo_x List"
1696 msgstr ""
1697
1698 #. Menu item
1699-#: zim/gui/pageview.py:132
1700+#: zim/gui/pageview.py:138
1701 msgid "Text From _File..."
1702 msgstr ""
1703
1704 #. Menu item
1705-#: zim/gui/pageview.py:133
1706+#: zim/gui/pageview.py:139
1707 msgid "_Link..."
1708 msgstr ""
1709
1710 #. Menu item |
1711 #. Dialog title
1712-#: zim/gui/pageview.py:133 zim/gui/pageview.py:6354
1713+#: zim/gui/pageview.py:139 zim/gui/pageview.py:6539
1714 msgid "Insert Link"
1715 msgstr ""
1716
1717 #. Menu item
1718-#: zim/gui/pageview.py:134
1719+#: zim/gui/pageview.py:140
1720 msgid "_Clear Formatting"
1721 msgstr ""
1722
1723 #. Menu item
1724-#: zim/gui/pageview.py:135
1725+#: zim/gui/pageview.py:141
1726 msgid "_Find..."
1727 msgstr ""
1728
1729 #. Menu item
1730-#: zim/gui/pageview.py:137
1731+#: zim/gui/pageview.py:143
1732 msgid "Find Ne_xt"
1733 msgstr ""
1734
1735 #. Menu item
1736-#: zim/gui/pageview.py:139
1737+#: zim/gui/pageview.py:145
1738 msgid "Find Pre_vious"
1739 msgstr ""
1740
1741 #. Menu item
1742-#: zim/gui/pageview.py:141
1743+#: zim/gui/pageview.py:147
1744 msgid "_Replace..."
1745 msgstr ""
1746
1747 #. Menu item
1748-#: zim/gui/pageview.py:142
1749+#: zim/gui/pageview.py:148
1750 msgid "Word Count..."
1751 msgstr ""
1752
1753 #. Menu item
1754-#: zim/gui/pageview.py:143
1755+#: zim/gui/pageview.py:149
1756 msgid "_Zoom In"
1757 msgstr ""
1758
1759 #. Menu item
1760-#: zim/gui/pageview.py:145
1761+#: zim/gui/pageview.py:151
1762 msgid "Zoom _Out"
1763 msgstr ""
1764
1765 #. Menu item to reset zoom
1766-#: zim/gui/pageview.py:146
1767+#: zim/gui/pageview.py:152
1768 msgid "_Normal Size"
1769 msgstr ""
1770
1771 #. Menu title
1772-#: zim/gui/pageview.py:149
1773+#: zim/gui/pageview.py:155
1774 msgid "New _Attachment"
1775 msgstr ""
1776
1777 #. Menu item in "Insert > New File Attachment" submenu
1778-#: zim/gui/pageview.py:152
1779+#: zim/gui/pageview.py:158
1780 msgid "File _Templates..."
1781 msgstr ""
1782
1783 #. Menu item
1784-#: zim/gui/pageview.py:157
1785+#: zim/gui/pageview.py:163
1786 msgid "Heading _1"
1787 msgstr ""
1788
1789 #. Menu item
1790-#: zim/gui/pageview.py:157
1791+#: zim/gui/pageview.py:163
1792 msgid "Heading 1"
1793 msgstr ""
1794
1795 #. Menu item
1796-#: zim/gui/pageview.py:158
1797+#: zim/gui/pageview.py:164
1798 msgid "Heading _2"
1799 msgstr ""
1800
1801 #. Menu item
1802-#: zim/gui/pageview.py:158
1803+#: zim/gui/pageview.py:164
1804 msgid "Heading 2"
1805 msgstr ""
1806
1807 #. Menu item
1808-#: zim/gui/pageview.py:159
1809+#: zim/gui/pageview.py:165
1810 msgid "Heading _3"
1811 msgstr ""
1812
1813 #. Menu item
1814-#: zim/gui/pageview.py:159
1815+#: zim/gui/pageview.py:165
1816 msgid "Heading 3"
1817 msgstr ""
1818
1819 #. Menu item
1820-#: zim/gui/pageview.py:160
1821+#: zim/gui/pageview.py:166
1822 msgid "Heading _4"
1823 msgstr ""
1824
1825 #. Menu item
1826-#: zim/gui/pageview.py:160
1827+#: zim/gui/pageview.py:166
1828 msgid "Heading 4"
1829 msgstr ""
1830
1831 #. Menu item
1832-#: zim/gui/pageview.py:161
1833+#: zim/gui/pageview.py:167
1834 msgid "Heading _5"
1835 msgstr ""
1836
1837 #. Menu item
1838-#: zim/gui/pageview.py:161
1839+#: zim/gui/pageview.py:167
1840 msgid "Heading 5"
1841 msgstr ""
1842
1843 #. Menu item
1844-#: zim/gui/pageview.py:162 zim/gui/pageview.py:173
1845+#: zim/gui/pageview.py:168 zim/gui/pageview.py:179
1846 msgid "_Strong"
1847 msgstr ""
1848
1849 #. Menu item
1850-#: zim/gui/pageview.py:162 zim/gui/pageview.py:173
1851+#: zim/gui/pageview.py:168 zim/gui/pageview.py:179
1852 msgid "Strong"
1853 msgstr ""
1854
1855 #. Menu item
1856-#: zim/gui/pageview.py:163 zim/gui/pageview.py:174
1857+#: zim/gui/pageview.py:169 zim/gui/pageview.py:180
1858 msgid "_Emphasis"
1859 msgstr ""
1860
1861 #. Menu item
1862-#: zim/gui/pageview.py:163 zim/gui/pageview.py:174
1863+#: zim/gui/pageview.py:169 zim/gui/pageview.py:180
1864 msgid "Emphasis"
1865 msgstr ""
1866
1867 #. Menu item
1868-#: zim/gui/pageview.py:164 zim/gui/pageview.py:175
1869+#: zim/gui/pageview.py:170 zim/gui/pageview.py:181
1870 msgid "_Mark"
1871 msgstr ""
1872
1873 #. Menu item
1874-#: zim/gui/pageview.py:164 zim/gui/pageview.py:175
1875+#: zim/gui/pageview.py:170 zim/gui/pageview.py:181
1876 msgid "Mark"
1877 msgstr ""
1878
1879 #. Menu item
1880-#: zim/gui/pageview.py:165 zim/gui/pageview.py:176
1881+#: zim/gui/pageview.py:171 zim/gui/pageview.py:182
1882 msgid "_Strike"
1883 msgstr ""
1884
1885 #. Menu item
1886-#: zim/gui/pageview.py:165 zim/gui/pageview.py:176
1887+#: zim/gui/pageview.py:171 zim/gui/pageview.py:182
1888 msgid "Strike"
1889 msgstr ""
1890
1891 #. Menu item
1892-#: zim/gui/pageview.py:166
1893+#: zim/gui/pageview.py:172
1894 msgid "_Subscript"
1895 msgstr ""
1896
1897 #. Menu item
1898-#: zim/gui/pageview.py:167
1899+#: zim/gui/pageview.py:173
1900 msgid "_Superscript"
1901 msgstr ""
1902
1903 #. Menu item
1904-#: zim/gui/pageview.py:168
1905+#: zim/gui/pageview.py:174
1906 msgid "_Verbatim"
1907 msgstr ""
1908
1909 #. Menu item
1910-#: zim/gui/pageview.py:168
1911+#: zim/gui/pageview.py:174
1912 msgid "Verbatim"
1913 msgstr ""
1914
1915 #. option in preferences dialog
1916-#: zim/gui/pageview.py:183
1917+#: zim/gui/pageview.py:189
1918 msgid ""
1919 "Use the <Enter> key to follow links\n"
1920 "(If disabled you can still use <Alt><Enter>)"
1921 msgstr ""
1922
1923 #. option in preferences dialog
1924-#: zim/gui/pageview.py:186
1925+#: zim/gui/pageview.py:192
1926 msgid "Show the cursor also for pages that can not be edited"
1927 msgstr ""
1928
1929 #. option in preferences dialog
1930-#: zim/gui/pageview.py:189
1931+#: zim/gui/pageview.py:195
1932 msgid "Automatically turn \"CamelCase\" words into links"
1933 msgstr ""
1934
1935 #. option in preferences dialog
1936-#: zim/gui/pageview.py:192
1937+#: zim/gui/pageview.py:198
1938 msgid "Automatically turn file paths into links"
1939 msgstr ""
1940
1941 #. option in preferences dialog
1942-#: zim/gui/pageview.py:195
1943+#: zim/gui/pageview.py:201
1944 msgid "Automatically select the current word when you apply formatting"
1945 msgstr ""
1946
1947 #. option in preferences dialog
1948-#: zim/gui/pageview.py:198
1949+#: zim/gui/pageview.py:204
1950 msgid ""
1951 "Unindent on <BackSpace>\n"
1952 "(If disabled you can still use <Shift><Tab>)"
1953 msgstr ""
1954
1955 #. option in preferences dialog
1956-#: zim/gui/pageview.py:201
1957+#: zim/gui/pageview.py:207
1958 msgid "Repeated clicking a checkbox cyles through the checkbox states"
1959 msgstr ""
1960
1961 #. option in preferences dialog
1962-#: zim/gui/pageview.py:204
1963+#: zim/gui/pageview.py:210
1964 msgid "(Un-)Indenting a list item also change any sub-items"
1965 msgstr ""
1966
1967 #. option in preferences dialog
1968-#: zim/gui/pageview.py:207
1969+#: zim/gui/pageview.py:213
1970 msgid "Checking a checkbox also change any sub-items"
1971 msgstr ""
1972
1973 #. option in preferences dialog
1974-#: zim/gui/pageview.py:210
1975+#: zim/gui/pageview.py:216
1976 msgid "Reformat wiki markup on the fly"
1977 msgstr ""
1978
1979 #. option in preferences dialog
1980-#: zim/gui/pageview.py:213
1981+#: zim/gui/pageview.py:219
1982 msgid "Default format for copying text to the clipboard"
1983 msgstr ""
1984
1985 #. option in preferences dialog
1986-#: zim/gui/pageview.py:216
1987+#: zim/gui/pageview.py:222
1988 msgid "Folder with templates for attachment files"
1989 msgstr ""
1990
1991 #. error when unknown interwiki link is clicked
1992-#: zim/gui/pageview.py:5216
1993+#: zim/gui/pageview.py:5369
1994 #, python-format
1995 msgid "No such wiki defined: %s"
1996 msgstr ""
1997
1998 #. menu item for context menu of editor
1999-#: zim/gui/pageview.py:5269
2000+#: zim/gui/pageview.py:5422
2001 msgid "Copy _As..."
2002 msgstr ""
2003
2004 #. Context menu item for pageview to move selected text to new/other page
2005-#: zim/gui/pageview.py:5276
2006+#: zim/gui/pageview.py:5429
2007 msgid "Move Selected Text..."
2008 msgstr ""
2009
2010 #. menu item in context menu for image
2011-#: zim/gui/pageview.py:5334
2012+#: zim/gui/pageview.py:5487
2013 msgid "_Edit Properties"
2014 msgstr ""
2015
2016 #. menu item in context menu
2017-#: zim/gui/pageview.py:5336
2018+#: zim/gui/pageview.py:5489
2019 msgid "_Edit Link"
2020 msgstr ""
2021
2022 #. context menu item
2023-#: zim/gui/pageview.py:5356 zim/gui/pageview.py:5360 zim/gui/pageview.py:5367
2024+#: zim/gui/pageview.py:5509 zim/gui/pageview.py:5513 zim/gui/pageview.py:5520
2025 msgid "Copy _Link"
2026 msgstr ""
2027
2028 #. context menu item
2029-#: zim/gui/pageview.py:5364
2030+#: zim/gui/pageview.py:5517
2031 msgid "Copy Email Address"
2032 msgstr ""
2033
2034 #. menu item to open containing folder of files
2035-#: zim/gui/pageview.py:5375
2036+#: zim/gui/pageview.py:5528
2037 msgid "Open Folder"
2038 msgstr ""
2039
2040 #. menu item for sub menu with applications |
2041 #. menu item
2042-#: zim/gui/pageview.py:5384 zim/gui/pageview.py:5395
2043-#: zim/plugins/attachmentbrowser.py:687
2044+#: zim/gui/pageview.py:5537 zim/gui/pageview.py:5548
2045+#: zim/plugins/attachmentbrowser.py:688
2046 msgid "Open With..."
2047 msgstr ""
2048
2049 #. menu item to open a link or file |
2050 #. menu item to open file or folder
2051-#: zim/gui/pageview.py:5415 zim/plugins/attachmentbrowser.py:693
2052+#: zim/gui/pageview.py:5568 zim/plugins/attachmentbrowser.py:694
2053 msgid "_Open"
2054 msgstr ""
2055
2056 #. message when no file templates are found in ~/Templates
2057-#: zim/gui/pageview.py:5654
2058+#: zim/gui/pageview.py:5810
2059 msgid "No templates installed"
2060 msgstr ""
2061
2062 #. Text in a question dialog for creating a folder, %s is the folder path
2063-#: zim/gui/pageview.py:5717
2064+#: zim/gui/pageview.py:5873
2065 #, python-format
2066 msgid ""
2067 "The folder\n"
2068@@ -1574,317 +1585,317 @@
2069 msgstr ""
2070
2071 #. Dialog title
2072-#: zim/gui/pageview.py:6020
2073+#: zim/gui/pageview.py:6205
2074 msgid "Insert Date and Time"
2075 msgstr ""
2076
2077 #. expander label in "insert date" dialog
2078-#: zim/gui/pageview.py:6049
2079+#: zim/gui/pageview.py:6234
2080 msgid "_Calendar"
2081 msgstr ""
2082
2083 #. check box in InsertDate dialog
2084-#: zim/gui/pageview.py:6063
2085+#: zim/gui/pageview.py:6248
2086 msgid "_Link to date"
2087 msgstr ""
2088
2089 #. Dialog title
2090-#: zim/gui/pageview.py:6156
2091+#: zim/gui/pageview.py:6341
2092 msgid "Insert Image"
2093 msgstr ""
2094
2095 #. checkbox in the "Insert Image" dialog
2096-#: zim/gui/pageview.py:6163
2097+#: zim/gui/pageview.py:6348
2098 msgid "Attach image first"
2099 msgstr ""
2100
2101 #. Error message when trying to insert a not supported file as image
2102-#: zim/gui/pageview.py:6178
2103+#: zim/gui/pageview.py:6363
2104 #, python-format
2105 msgid "File type not supported: %s"
2106 msgstr ""
2107
2108 #. Dialog title
2109-#: zim/gui/pageview.py:6206
2110+#: zim/gui/pageview.py:6391
2111 msgid "Edit Image"
2112 msgstr ""
2113
2114 #. Input in 'edit image' dialog
2115-#: zim/gui/pageview.py:6225
2116+#: zim/gui/pageview.py:6410
2117 msgid "Location"
2118 msgstr ""
2119
2120 #. Input in 'edit image' dialog |
2121 #. Input in 'insert link' dialog
2122-#: zim/gui/pageview.py:6226 zim/gui/pageview.py:6360
2123+#: zim/gui/pageview.py:6411 zim/gui/pageview.py:6545
2124 msgid "Link to"
2125 msgstr ""
2126
2127 #. Input in 'edit image' dialog
2128-#: zim/gui/pageview.py:6227
2129+#: zim/gui/pageview.py:6412
2130 msgid "Width"
2131 msgstr ""
2132
2133 #. Input in 'edit image' dialog
2134-#: zim/gui/pageview.py:6228
2135+#: zim/gui/pageview.py:6413
2136 msgid "Height"
2137 msgstr ""
2138
2139 #. Button in 'edit image' dialog
2140-#: zim/gui/pageview.py:6236
2141+#: zim/gui/pageview.py:6421
2142 msgid "_Reset Size"
2143 msgstr ""
2144
2145 #. Dialog title
2146-#: zim/gui/pageview.py:6331
2147+#: zim/gui/pageview.py:6516
2148 msgid "Insert Text From File"
2149 msgstr ""
2150
2151 #. Dialog title
2152-#: zim/gui/pageview.py:6353
2153+#: zim/gui/pageview.py:6538
2154 msgid "Edit Link"
2155 msgstr ""
2156
2157 #. Dialog button
2158-#: zim/gui/pageview.py:6357
2159+#: zim/gui/pageview.py:6542
2160 msgid "_Link"
2161 msgstr ""
2162
2163 #. Input in 'insert link' dialog
2164-#: zim/gui/pageview.py:6361
2165+#: zim/gui/pageview.py:6546
2166 msgid "Text"
2167 msgstr ""
2168
2169 #. button in find bar and find & replace dialog
2170-#: zim/gui/pageview.py:6460
2171+#: zim/gui/pageview.py:6645
2172 msgid "_Next"
2173 msgstr ""
2174
2175 #. button in find bar and find & replace dialog
2176-#: zim/gui/pageview.py:6466
2177+#: zim/gui/pageview.py:6651
2178 msgid "_Previous"
2179 msgstr ""
2180
2181 #. checkbox option in find bar and find & replace dialog
2182-#: zim/gui/pageview.py:6472
2183+#: zim/gui/pageview.py:6657
2184 msgid "Match _case"
2185 msgstr ""
2186
2187 #. checkbox option in find bar and find & replace dialog
2188-#: zim/gui/pageview.py:6477
2189+#: zim/gui/pageview.py:6662
2190 msgid "Whole _word"
2191 msgstr ""
2192
2193 #. checkbox option in find bar and find & replace dialog
2194-#: zim/gui/pageview.py:6482
2195+#: zim/gui/pageview.py:6667
2196 msgid "_Regular expression"
2197 msgstr ""
2198
2199 #. checkbox option in find bar and find & replace dialog
2200-#: zim/gui/pageview.py:6487
2201+#: zim/gui/pageview.py:6672
2202 msgid "_Highlight"
2203 msgstr ""
2204
2205 #. label for input in find bar on bottom of page
2206-#: zim/gui/pageview.py:6573
2207+#: zim/gui/pageview.py:6758
2208 msgid "Find"
2209 msgstr ""
2210
2211 #. Options button
2212-#: zim/gui/pageview.py:6603
2213+#: zim/gui/pageview.py:6788
2214 msgid "Options"
2215 msgstr ""
2216
2217 #. Dialog title
2218-#: zim/gui/pageview.py:6648
2219+#: zim/gui/pageview.py:6833
2220 msgid "Find and Replace"
2221 msgstr ""
2222
2223 #. input label in find & replace dialog
2224-#: zim/gui/pageview.py:6658
2225+#: zim/gui/pageview.py:6843
2226 msgid "Find what"
2227 msgstr ""
2228
2229 #. input label in find & replace dialog
2230-#: zim/gui/pageview.py:6668
2231+#: zim/gui/pageview.py:6853
2232 msgid "Replace with"
2233 msgstr ""
2234
2235 #. Button in search & replace dialog
2236-#: zim/gui/pageview.py:6680
2237+#: zim/gui/pageview.py:6865
2238 msgid "_Replace"
2239 msgstr ""
2240
2241 #. Button in search & replace dialog
2242-#: zim/gui/pageview.py:6685
2243+#: zim/gui/pageview.py:6870
2244 msgid "Replace _All"
2245 msgstr ""
2246
2247 #. Dialog title
2248-#: zim/gui/pageview.py:6712
2249+#: zim/gui/pageview.py:6897
2250 msgid "Word Count"
2251 msgstr ""
2252
2253 #. label in word count dialog
2254-#: zim/gui/pageview.py:6755
2255+#: zim/gui/pageview.py:6940
2256 msgid "Paragraph"
2257 msgstr ""
2258
2259 #. label in word count dialog
2260-#: zim/gui/pageview.py:6756
2261+#: zim/gui/pageview.py:6941
2262 msgid "Selection"
2263 msgstr ""
2264
2265 #. label in word count dialog
2266-#: zim/gui/pageview.py:6757
2267+#: zim/gui/pageview.py:6942
2268 msgid "Words"
2269 msgstr ""
2270
2271 #. label in word count dialog
2272-#: zim/gui/pageview.py:6758
2273+#: zim/gui/pageview.py:6943
2274 msgid "Lines"
2275 msgstr ""
2276
2277 #. label in word count dialog
2278-#: zim/gui/pageview.py:6759
2279+#: zim/gui/pageview.py:6944
2280 msgid "Characters"
2281 msgstr ""
2282
2283 #. label in word count dialog
2284-#: zim/gui/pageview.py:6760
2285+#: zim/gui/pageview.py:6945
2286 msgid "Characters excluding spaces"
2287 msgstr ""
2288
2289 #. Dialog title
2290-#: zim/gui/pageview.py:6799
2291+#: zim/gui/pageview.py:6984
2292 msgid "Move Text to Other Page"
2293 msgstr ""
2294
2295 #. Button label
2296-#: zim/gui/pageview.py:6800
2297+#: zim/gui/pageview.py:6985
2298 msgid "_Move"
2299 msgstr ""
2300
2301 #. Input in 'move text' dialog
2302-#: zim/gui/pageview.py:6815
2303+#: zim/gui/pageview.py:7000
2304 msgid "Move text to"
2305 msgstr ""
2306
2307 #. Input in 'move text' dialog
2308-#: zim/gui/pageview.py:6816
2309+#: zim/gui/pageview.py:7001
2310 msgid "Leave link to new page"
2311 msgstr ""
2312
2313 #. Input in 'move text' dialog
2314-#: zim/gui/pageview.py:6817
2315+#: zim/gui/pageview.py:7002
2316 msgid "Open new page"
2317 msgstr ""
2318
2319 #. Dialog title
2320-#: zim/gui/pageview.py:6855
2321+#: zim/gui/pageview.py:7040
2322 msgid "New File"
2323 msgstr ""
2324
2325 #. Tab in preferences dialog
2326+#: zim/gui/preferencesdialog.py:20
2327+msgid "Interface"
2328+msgstr ""
2329+
2330+#. Tab in preferences dialog
2331 #: zim/gui/preferencesdialog.py:21
2332-msgid "Interface"
2333-msgstr ""
2334-
2335-#. Tab in preferences dialog
2336-#: zim/gui/preferencesdialog.py:22
2337 msgid "Editing"
2338 msgstr ""
2339
2340 #. Dialog title
2341-#: zim/gui/preferencesdialog.py:32
2342+#: zim/gui/preferencesdialog.py:31
2343 msgid "Preferences"
2344 msgstr ""
2345
2346 #. Heading in preferences dialog
2347-#: zim/gui/preferencesdialog.py:78
2348+#: zim/gui/preferencesdialog.py:77
2349 msgid "Plugins"
2350 msgstr ""
2351
2352 #. Heading in preferences dialog
2353-#: zim/gui/preferencesdialog.py:88
2354+#: zim/gui/preferencesdialog.py:87
2355 msgid "Applications"
2356 msgstr ""
2357
2358 #. option in preferences dialog
2359-#: zim/gui/preferencesdialog.py:95
2360+#: zim/gui/preferencesdialog.py:94
2361 msgid "Use a custom font"
2362 msgstr ""
2363
2364 #. Button in plugin tab
2365-#: zim/gui/preferencesdialog.py:209
2366+#: zim/gui/preferencesdialog.py:208
2367 msgid "_More"
2368 msgstr ""
2369
2370 #. Button in plugin tab
2371-#: zim/gui/preferencesdialog.py:214
2372+#: zim/gui/preferencesdialog.py:213
2373 msgid "C_onfigure"
2374 msgstr ""
2375
2376 #. label for button with URL
2377-#: zim/gui/preferencesdialog.py:223
2378+#: zim/gui/preferencesdialog.py:226
2379 msgid "Get more plugins online"
2380 msgstr ""
2381
2382 #. Heading in plugins tab of preferences dialog
2383-#: zim/gui/preferencesdialog.py:248
2384+#: zim/gui/preferencesdialog.py:251
2385 msgid "Dependencies"
2386 msgstr ""
2387
2388 #. label in plugin info in preferences dialog
2389-#: zim/gui/preferencesdialog.py:252
2390+#: zim/gui/preferencesdialog.py:255
2391 msgid "No dependencies"
2392 msgstr ""
2393
2394 #. dependency is OK
2395-#: zim/gui/preferencesdialog.py:258
2396+#: zim/gui/preferencesdialog.py:261
2397 msgid "OK"
2398 msgstr ""
2399
2400 #. dependency failed
2401-#: zim/gui/preferencesdialog.py:260 zim/gui/preferencesdialog.py:263
2402+#: zim/gui/preferencesdialog.py:263 zim/gui/preferencesdialog.py:266
2403 msgid "Failed"
2404 msgstr ""
2405
2406 #. optional dependency
2407-#: zim/gui/preferencesdialog.py:264
2408+#: zim/gui/preferencesdialog.py:267
2409 msgid "Optional"
2410 msgstr ""
2411
2412 #. Heading in plugins tab of preferences dialog |
2413 #. Column header versions dialog
2414-#: zim/gui/preferencesdialog.py:268
2415+#: zim/gui/preferencesdialog.py:271
2416 #: zim/plugins/versioncontrol/__init__.py:1180
2417 msgid "Author"
2418 msgstr ""
2419
2420 #. Column in plugin tab
2421-#: zim/gui/preferencesdialog.py:349
2422+#: zim/gui/preferencesdialog.py:353
2423 msgid "Enabled"
2424 msgstr ""
2425
2426 #. Column in plugin tab
2427-#: zim/gui/preferencesdialog.py:352
2428+#: zim/gui/preferencesdialog.py:356
2429 msgid "Plugin"
2430 msgstr ""
2431
2432 #. Dialog title
2433-#: zim/gui/preferencesdialog.py:359
2434+#: zim/gui/preferencesdialog.py:363
2435 msgid "Configure Plugin"
2436 msgstr ""
2437
2438 #. Heading for 'configure plugin' dialog - %s is the plugin name
2439-#: zim/gui/preferencesdialog.py:364
2440+#: zim/gui/preferencesdialog.py:368
2441 #, python-format
2442 msgid "Options for plugin %s"
2443 msgstr ""
2444
2445 #. button in preferences dialog to change default text editor
2446-#: zim/gui/preferencesdialog.py:398
2447+#: zim/gui/preferencesdialog.py:402
2448 msgid "Set default text editor"
2449 msgstr ""
2450
2451@@ -1926,12 +1937,12 @@
2452 msgstr ""
2453
2454 #. checkbox option in search dialog
2455-#: zim/gui/searchdialog.py:58
2456+#: zim/gui/searchdialog.py:59
2457 msgid "Limit search to the current page and sub-pages"
2458 msgstr ""
2459
2460 #. Column header search dialog
2461-#: zim/gui/searchdialog.py:151
2462+#: zim/gui/searchdialog.py:152
2463 msgid "Score"
2464 msgstr ""
2465
2466@@ -1946,22 +1957,22 @@
2467 msgstr ""
2468
2469 #. Checkbox in web server gui
2470-#: zim/gui/server.py:81
2471+#: zim/gui/server.py:82
2472 msgid "Allow public access"
2473 msgstr ""
2474
2475 #. Field in web server gui for HTTP port (e.g. port 80)
2476-#: zim/gui/server.py:99
2477+#: zim/gui/server.py:100
2478 msgid "Port"
2479 msgstr ""
2480
2481 #. Status in web server gui
2482-#: zim/gui/server.py:158
2483+#: zim/gui/server.py:159
2484 msgid "Server started"
2485 msgstr ""
2486
2487 #. Status in web server gui
2488-#: zim/gui/server.py:199
2489+#: zim/gui/server.py:200
2490 msgid "Server stopped"
2491 msgstr ""
2492
2493@@ -1977,114 +1988,114 @@
2494 msgstr ""
2495
2496 #. Dialog title
2497-#: zim/gui/templateeditordialog.py:155
2498+#: zim/gui/templateeditordialog.py:156
2499 msgid "Copy Template"
2500 msgstr ""
2501
2502 #. dialog title
2503-#: zim/gui/widgets.py:572 zim/gui/widgets.py:1753
2504+#: zim/gui/widgets.py:573 zim/gui/widgets.py:1759
2505 msgid "Select File"
2506 msgstr ""
2507
2508 #. menu item in context menu
2509-#: zim/gui/widgets.py:710
2510+#: zim/gui/widgets.py:712
2511 msgid "Expand _All"
2512 msgstr ""
2513
2514 #. menu item in context menu
2515-#: zim/gui/widgets.py:712
2516+#: zim/gui/widgets.py:714
2517 msgid "_Collapse All"
2518 msgstr ""
2519
2520 #. tooltip for the inline icon to clear a text entry widget
2521-#: zim/gui/widgets.py:1555
2522+#: zim/gui/widgets.py:1561
2523 msgid "Clear"
2524 msgstr ""
2525
2526 #. dialog title
2527-#: zim/gui/widgets.py:1749
2528+#: zim/gui/widgets.py:1755
2529 msgid "Select Folder"
2530 msgstr ""
2531
2532 #. dialog title
2533-#: zim/gui/widgets.py:1751
2534+#: zim/gui/widgets.py:1757
2535 msgid "Select Image"
2536 msgstr ""
2537
2538 #. default text for empty page section selection
2539-#: zim/gui/widgets.py:1884
2540+#: zim/gui/widgets.py:1890
2541 msgid "<Top>"
2542 msgstr ""
2543
2544 #. Option for placement of plugin widgets
2545-#: zim/gui/widgets.py:2178
2546+#: zim/gui/widgets.py:2184
2547 msgid "Left Side Pane"
2548 msgstr ""
2549
2550 #. Option for placement of plugin widgets
2551-#: zim/gui/widgets.py:2179
2552+#: zim/gui/widgets.py:2185
2553 msgid "Right Side Pane"
2554 msgstr ""
2555
2556 #. Option for placement of plugin widgets
2557-#: zim/gui/widgets.py:2180
2558+#: zim/gui/widgets.py:2186
2559 msgid "Bottom Pane"
2560 msgstr ""
2561
2562 #. Option for placement of plugin widgets
2563-#: zim/gui/widgets.py:2181
2564+#: zim/gui/widgets.py:2187
2565 msgid "Top Pane"
2566 msgstr ""
2567
2568 #. Option for placement of plugin widgets
2569-#: zim/gui/widgets.py:2185
2570+#: zim/gui/widgets.py:2191
2571 msgid "Top Left"
2572 msgstr ""
2573
2574 #. Option for placement of plugin widgets
2575-#: zim/gui/widgets.py:2186
2576+#: zim/gui/widgets.py:2192
2577 msgid "Bottom Left"
2578 msgstr ""
2579
2580 #. Option for placement of plugin widgets
2581-#: zim/gui/widgets.py:2187
2582+#: zim/gui/widgets.py:2193
2583 msgid "Top Right"
2584 msgstr ""
2585
2586 #. Option for placement of plugin widgets
2587-#: zim/gui/widgets.py:2188
2588+#: zim/gui/widgets.py:2194
2589 msgid "Bottom Right"
2590 msgstr ""
2591
2592 #. generic error dialog text
2593-#: zim/gui/widgets.py:3231
2594+#: zim/gui/widgets.py:3241
2595 msgid ""
2596 "When reporting this bug please include\n"
2597 "the information from the text box below"
2598 msgstr ""
2599
2600 #. Filter in open file dialog, shows all files (*)
2601-#: zim/gui/widgets.py:3552
2602+#: zim/gui/widgets.py:3562
2603 msgid "All Files"
2604 msgstr ""
2605
2606 #. Filter in open file dialog, shows image files only
2607-#: zim/gui/widgets.py:3579
2608+#: zim/gui/widgets.py:3589
2609 msgid "Images"
2610 msgstr ""
2611
2612 #. dialog title for log view dialog - e.g. for Equation Editor
2613-#: zim/gui/widgets.py:3775
2614+#: zim/gui/widgets.py:3785
2615 msgid "Log file"
2616 msgstr ""
2617
2618 #. Dialog title
2619-#: zim/gui/widgets.py:4246
2620+#: zim/gui/widgets.py:4256
2621 msgid "File Exists"
2622 msgstr ""
2623
2624 #. Dialog text in 'new filename' dialog
2625-#: zim/gui/widgets.py:4247
2626+#: zim/gui/widgets.py:4257
2627 #, python-format
2628 msgid ""
2629 "A file with the name <b>\"%s\"</b> already exists.\n"
2630@@ -2092,17 +2103,17 @@
2631 msgstr ""
2632
2633 #. Input label
2634-#: zim/gui/widgets.py:4256
2635+#: zim/gui/widgets.py:4266
2636 msgid "Filename"
2637 msgstr ""
2638
2639 #. Button label
2640-#: zim/gui/widgets.py:4265
2641+#: zim/gui/widgets.py:4275
2642 msgid "_Browse"
2643 msgstr ""
2644
2645 #. Button label
2646-#: zim/gui/widgets.py:4270
2647+#: zim/gui/widgets.py:4280
2648 msgid "Overwrite"
2649 msgstr ""
2650
2651@@ -2192,27 +2203,6 @@
2652 msgid "Profile"
2653 msgstr ""
2654
2655-#. Label for object manager
2656-#: zim/objectmanager.py:211
2657-#, python-format
2658-msgid "Plugin %s is required to display this object."
2659-msgstr ""
2660-
2661-#. Label for object manager
2662-#: zim/objectmanager.py:216
2663-msgid "Enable plugin"
2664-msgstr ""
2665-
2666-#. Label for object manager
2667-#: zim/objectmanager.py:228
2668-msgid "Show plugin details"
2669-msgstr ""
2670-
2671-#. Label for object manager
2672-#: zim/objectmanager.py:232
2673-msgid "No plugin is available to display this object."
2674-msgstr ""
2675-
2676 #. placeholder for unknown file name
2677 #: zim/parser.py:269
2678 msgid "<Unknown>"
2679@@ -2284,37 +2274,37 @@
2680 msgstr[1] ""
2681
2682 #. unspecified value for file modification time
2683-#: zim/plugins/attachmentbrowser.py:712
2684+#: zim/plugins/attachmentbrowser.py:713
2685 msgid "Unknown"
2686 msgstr ""
2687
2688 #. label for file type
2689-#: zim/plugins/attachmentbrowser.py:727
2690+#: zim/plugins/attachmentbrowser.py:728
2691 msgid "Type"
2692 msgstr ""
2693
2694 #. label for file size
2695-#: zim/plugins/attachmentbrowser.py:728
2696+#: zim/plugins/attachmentbrowser.py:729
2697 msgid "Size"
2698 msgstr ""
2699
2700 #. label for file modification date
2701-#: zim/plugins/attachmentbrowser.py:729
2702+#: zim/plugins/attachmentbrowser.py:730
2703 msgid "Modified"
2704 msgstr ""
2705
2706 #. popup menu action on drag-drop of a file
2707-#: zim/plugins/attachmentbrowser.py:764
2708+#: zim/plugins/attachmentbrowser.py:765
2709 msgid "_Move Here"
2710 msgstr ""
2711
2712 #. popup menu action on drag-drop of a file
2713-#: zim/plugins/attachmentbrowser.py:768
2714+#: zim/plugins/attachmentbrowser.py:769
2715 msgid "_Copy Here"
2716 msgstr ""
2717
2718 #. popup menu action on drag-drop of a file
2719-#: zim/plugins/attachmentbrowser.py:773
2720+#: zim/plugins/attachmentbrowser.py:774
2721 msgid "Cancel"
2722 msgstr ""
2723
2724@@ -2392,33 +2382,38 @@
2725 msgid "Use a page for each"
2726 msgstr ""
2727
2728+#. preferences option
2729+#: zim/plugins/calendar.py:102
2730+msgid "Expand journal page in index when opened"
2731+msgstr ""
2732+
2733 #. menu item
2734-#: zim/plugins/calendar.py:229
2735+#: zim/plugins/calendar.py:232
2736 msgid "To_day"
2737 msgstr ""
2738
2739 #. menu item
2740-#: zim/plugins/calendar.py:266
2741+#: zim/plugins/calendar.py:269
2742 msgid "Calen_dar"
2743 msgstr ""
2744
2745 #. menu item
2746-#: zim/plugins/calendar.py:266
2747+#: zim/plugins/calendar.py:269
2748 msgid "Show calendar"
2749 msgstr ""
2750
2751 #. strftime format for current date label
2752-#: zim/plugins/calendar.py:419
2753+#: zim/plugins/calendar.py:423
2754 msgid "%A %d %B %Y"
2755 msgstr ""
2756
2757 #. dialog title
2758-#: zim/plugins/calendar.py:471
2759+#: zim/plugins/calendar.py:475
2760 msgid "Calendar"
2761 msgstr ""
2762
2763 #. button label
2764-#: zim/plugins/calendar.py:481
2765+#: zim/plugins/calendar.py:485
2766 msgid "_Today"
2767 msgstr ""
2768
2769@@ -2745,7 +2740,7 @@
2770 msgstr ""
2771
2772 #. text entry field
2773-#: zim/plugins/quicknote.py:224
2774+#: zim/plugins/quicknote.py:224 zim/plugins/tableeditor.py:999
2775 msgid "Title"
2776 msgstr ""
2777
2778@@ -2869,64 +2864,64 @@
2779 msgstr ""
2780
2781 #. plugin name
2782-#: zim/plugins/sourceview.py:37
2783+#: zim/plugins/sourceview.py:43
2784 msgid "Source View"
2785 msgstr ""
2786
2787 #. plugin description
2788-#: zim/plugins/sourceview.py:38
2789+#: zim/plugins/sourceview.py:44
2790 msgid ""
2791 "This plugin allows inserting 'Code Blocks' in the page. These will be\n"
2792 "shown as emdedded widgets with syntax highlighting, line numbers etc.\n"
2793 msgstr ""
2794
2795 #. preference option for sourceview plugin
2796-#: zim/plugins/sourceview.py:49
2797+#: zim/plugins/sourceview.py:55
2798 msgid "Auto indenting"
2799 msgstr ""
2800
2801 #. preference option for sourceview plugin
2802-#: zim/plugins/sourceview.py:51
2803+#: zim/plugins/sourceview.py:57
2804 msgid "Smart Home key"
2805 msgstr ""
2806
2807 #. preference option for sourceview plugin
2808-#: zim/plugins/sourceview.py:53
2809+#: zim/plugins/sourceview.py:59
2810 msgid "Highlight current line"
2811 msgstr ""
2812
2813 #. preference option for sourceview plugin
2814-#: zim/plugins/sourceview.py:55
2815+#: zim/plugins/sourceview.py:61
2816 msgid "Show right margin"
2817 msgstr ""
2818
2819 #. preference option for sourceview plugin
2820-#: zim/plugins/sourceview.py:57
2821+#: zim/plugins/sourceview.py:63
2822 msgid "Right margin position"
2823 msgstr ""
2824
2825 #. preference option for sourceview plugin
2826-#: zim/plugins/sourceview.py:59
2827+#: zim/plugins/sourceview.py:65
2828 msgid "Tab width"
2829 msgstr ""
2830
2831 #. menu item
2832-#: zim/plugins/sourceview.py:108
2833+#: zim/plugins/sourceview.py:112
2834 msgid "Code Block"
2835 msgstr ""
2836
2837 #. dialog title
2838-#: zim/plugins/sourceview.py:123
2839+#: zim/plugins/sourceview.py:127
2840 msgid "Insert Code Block"
2841 msgstr ""
2842
2843 #. input label
2844-#: zim/plugins/sourceview.py:126 zim/plugins/sourceview.py:361
2845+#: zim/plugins/sourceview.py:130 zim/plugins/sourceview.py:344
2846 msgid "Syntax"
2847 msgstr ""
2848
2849 #. preference option for sourceview plugin
2850-#: zim/plugins/sourceview.py:351
2851+#: zim/plugins/sourceview.py:334
2852 msgid "Show Line Numbers"
2853 msgstr ""
2854
2855@@ -2960,6 +2955,194 @@
2856 "dictionaries installed"
2857 msgstr ""
2858
2859+#. alignment option
2860+#: zim/plugins/tableeditor.py:56 zim/plugins/tableeditor.py:943
2861+msgid "Left"
2862+msgstr ""
2863+
2864+#. alignment option
2865+#: zim/plugins/tableeditor.py:57
2866+msgid "Center"
2867+msgstr ""
2868+
2869+#. alignment option
2870+#: zim/plugins/tableeditor.py:58
2871+msgid "Right"
2872+msgstr ""
2873+
2874+#. alignment option
2875+#: zim/plugins/tableeditor.py:59
2876+msgid "Unspecified"
2877+msgstr ""
2878+
2879+#. plugin name
2880+#: zim/plugins/tableeditor.py:90
2881+msgid "Table Editor"
2882+msgstr ""
2883+
2884+#. plugin description
2885+#: zim/plugins/tableeditor.py:91
2886+msgid ""
2887+"With this plugin you can embed a 'Table' into the wiki page. Tables will be "
2888+"shown as GTK TreeView widgets.\n"
2889+"Exporting them to various formats (i.e. HTML/LaTeX) completes the feature "
2890+"set.\n"
2891+msgstr ""
2892+
2893+#. option value
2894+#: zim/plugins/tableeditor.py:101
2895+msgid "with lines"
2896+msgstr ""
2897+
2898+#. option value
2899+#: zim/plugins/tableeditor.py:102
2900+msgid "no grid lines"
2901+msgstr ""
2902+
2903+#. option value
2904+#: zim/plugins/tableeditor.py:103
2905+msgid "horizontal lines"
2906+msgstr ""
2907+
2908+#. option value
2909+#: zim/plugins/tableeditor.py:104
2910+msgid "vertical lines"
2911+msgstr ""
2912+
2913+#. preference description
2914+#: zim/plugins/tableeditor.py:110
2915+msgid "Show helper toolbar"
2916+msgstr ""
2917+
2918+#. preference description
2919+#: zim/plugins/tableeditor.py:113
2920+msgid "Grid lines"
2921+msgstr ""
2922+
2923+#. menu item
2924+#: zim/plugins/tableeditor.py:252
2925+msgid "Table"
2926+msgstr ""
2927+
2928+#. tooltip on mouse hover |
2929+#. menu item
2930+#: zim/plugins/tableeditor.py:547 zim/plugins/tableeditor.py:759
2931+msgid "Add row"
2932+msgstr ""
2933+
2934+#. tooltip on mouse hover
2935+#: zim/plugins/tableeditor.py:548
2936+msgid "Remove row"
2937+msgstr ""
2938+
2939+#. tooltip on mouse hover |
2940+#. menu item
2941+#: zim/plugins/tableeditor.py:549 zim/plugins/tableeditor.py:761
2942+msgid "Clone row"
2943+msgstr ""
2944+
2945+#. tooltip on mouse hover |
2946+#. menu item
2947+#: zim/plugins/tableeditor.py:551 zim/plugins/tableeditor.py:765
2948+msgid "Row up"
2949+msgstr ""
2950+
2951+#. tooltip on mouse hover |
2952+#. menu item
2953+#: zim/plugins/tableeditor.py:552 zim/plugins/tableeditor.py:766
2954+msgid "Row down"
2955+msgstr ""
2956+
2957+#. tooltip on mouse hover |
2958+#. menu item
2959+#: zim/plugins/tableeditor.py:554 zim/plugins/tableeditor.py:768
2960+msgid "Change columns"
2961+msgstr ""
2962+
2963+#. tooltip on mouse hover
2964+#: zim/plugins/tableeditor.py:556
2965+msgid "Open help"
2966+msgstr ""
2967+
2968+#. menu item
2969+#: zim/plugins/tableeditor.py:760
2970+msgid "Delete row"
2971+msgstr ""
2972+
2973+#. menu item
2974+#: zim/plugins/tableeditor.py:763
2975+msgid "Open cell content link"
2976+msgstr ""
2977+
2978+#. Popup dialog
2979+#: zim/plugins/tableeditor.py:826
2980+msgid ""
2981+"The table must consist of at least on row!\n"
2982+" No deletion done."
2983+msgstr ""
2984+
2985+#. Popup dialog
2986+#: zim/plugins/tableeditor.py:909 zim/plugins/tableeditor.py:1157
2987+msgid "Please select a row, before you push the button."
2988+msgstr ""
2989+
2990+#. Dialog title
2991+#: zim/plugins/tableeditor.py:938
2992+msgid "Insert Table"
2993+msgstr ""
2994+
2995+#. Dialog title
2996+#: zim/plugins/tableeditor.py:938
2997+msgid "Edit Table"
2998+msgstr ""
2999+
3000+#. Description of "Table-Insert" Dialog
3001+#: zim/plugins/tableeditor.py:948
3002+msgid "Managing table columns"
3003+msgstr ""
3004+
3005+#. Initial data for column title in table
3006+#: zim/plugins/tableeditor.py:974
3007+msgid "Column 1"
3008+msgstr ""
3009+
3010+#. table header
3011+#: zim/plugins/tableeditor.py:1008
3012+msgid ""
3013+"Auto\n"
3014+"Wrap"
3015+msgstr ""
3016+
3017+#. table header
3018+#: zim/plugins/tableeditor.py:1018
3019+msgid "Align"
3020+msgstr ""
3021+
3022+#. hoover tooltip
3023+#: zim/plugins/tableeditor.py:1046
3024+msgid "Add column"
3025+msgstr ""
3026+
3027+#. hoover tooltip
3028+#: zim/plugins/tableeditor.py:1047
3029+msgid "Remove column"
3030+msgstr ""
3031+
3032+#. hoover tooltip
3033+#: zim/plugins/tableeditor.py:1048
3034+msgid "Move column ahead"
3035+msgstr ""
3036+
3037+#. hoover tooltip
3038+#: zim/plugins/tableeditor.py:1049
3039+msgid "Move column backward"
3040+msgstr ""
3041+
3042+#. popup dialog
3043+#: zim/plugins/tableeditor.py:1131
3044+msgid "A table needs to have at least one column."
3045+msgstr ""
3046+
3047 #. plugin name
3048 #: zim/plugins/tableofcontents.py:66
3049 msgid "Table of Contents"
3050@@ -3001,7 +3184,7 @@
3051
3052 #. plugin name |
3053 #. Column header for tag list in Task List dialog
3054-#: zim/plugins/tags.py:30 zim/plugins/tags.py:70 zim/plugins/tasklist.py:736
3055+#: zim/plugins/tags.py:30 zim/plugins/tags.py:70 zim/plugins/tasklist.py:739
3056 msgid "Tags"
3057 msgstr ""
3058
3059@@ -3013,25 +3196,30 @@
3060 msgstr ""
3061
3062 #. menu option
3063-#: zim/plugins/tags.py:125
3064+#: zim/plugins/tags.py:139
3065+msgid "Show full page name"
3066+msgstr ""
3067+
3068+#. menu option
3069+#: zim/plugins/tags.py:144
3070 msgid "Sort pages by tags"
3071 msgstr ""
3072
3073 #. label for untagged pages in side pane
3074-#: zim/plugins/tags.py:250
3075+#: zim/plugins/tags.py:270
3076 msgid "untagged"
3077 msgstr ""
3078
3079 #. Context menu item for tag cloud
3080-#: zim/plugins/tags.py:997
3081+#: zim/plugins/tags.py:1008
3082 msgid "Sort alphabetically"
3083 msgstr ""
3084
3085 #. plugin name |
3086 #. menu item |
3087 #. dialog title
3088-#: zim/plugins/tasklist.py:82 zim/plugins/tasklist.py:405
3089-#: zim/plugins/tasklist.py:631
3090+#: zim/plugins/tasklist.py:82 zim/plugins/tasklist.py:408
3091+#: zim/plugins/tasklist.py:634
3092 msgid "Task List"
3093 msgstr ""
3094
3095@@ -3091,12 +3279,12 @@
3096 msgstr ""
3097
3098 #. Short message text on first time use of task list plugin
3099-#: zim/plugins/tasklist.py:409
3100+#: zim/plugins/tasklist.py:412
3101 msgid "Need to index the notebook"
3102 msgstr ""
3103
3104 #. Long message text on first time use of task list plugin
3105-#: zim/plugins/tasklist.py:411
3106+#: zim/plugins/tasklist.py:414
3107 msgid ""
3108 "This is the first time the task list is opened.\n"
3109 "Therefore the index needs to be rebuild.\n"
3110@@ -3106,17 +3294,17 @@
3111 msgstr ""
3112
3113 #. Input label
3114-#: zim/plugins/tasklist.py:661
3115+#: zim/plugins/tasklist.py:664
3116 msgid "Filter"
3117 msgstr ""
3118
3119 #. Checkbox in task list
3120-#: zim/plugins/tasklist.py:681
3121+#: zim/plugins/tasklist.py:684
3122 msgid "Only Show Actionable Tasks"
3123 msgstr ""
3124
3125 #. Label for statistics in Task List, %i is the number of tasks
3126-#: zim/plugins/tasklist.py:694
3127+#: zim/plugins/tasklist.py:697
3128 #, python-format
3129 msgid "%i open item"
3130 msgid_plural "%i open items"
3131@@ -3124,23 +3312,23 @@
3132 msgstr[1] ""
3133
3134 #. "tag" for showing all tasks
3135-#: zim/plugins/tasklist.py:795
3136+#: zim/plugins/tasklist.py:798
3137 msgid "All Tasks"
3138 msgstr ""
3139
3140 #. label in tasklist plugins for tasks without a tag
3141-#: zim/plugins/tasklist.py:806
3142+#: zim/plugins/tasklist.py:809
3143 msgid "Untagged"
3144 msgstr ""
3145
3146 #. Column header Task List dialog
3147-#: zim/plugins/tasklist.py:890
3148+#: zim/plugins/tasklist.py:893
3149 msgid "Task"
3150 msgstr ""
3151
3152 #. Column header Task List dialog |
3153 #. Column header versions dialog
3154-#: zim/plugins/tasklist.py:935 zim/plugins/versioncontrol/__init__.py:1179
3155+#: zim/plugins/tasklist.py:939 zim/plugins/versioncontrol/__init__.py:1179
3156 msgid "Date"
3157 msgstr ""
3158
3159@@ -3375,7 +3563,7 @@
3160 msgstr ""
3161
3162 #. Error message in template lookup
3163-#: zim/templates/__init__.py:122
3164+#: zim/templates/__init__.py:121
3165 #, python-format
3166 msgid "Could not find template \"%s\""
3167 msgstr ""
3168
3169=== modified file 'zim/formats/__init__.py'
3170--- zim/formats/__init__.py 2014-11-11 20:26:12 +0000
3171+++ zim/formats/__init__.py 2015-04-04 11:26:41 +0000
3172@@ -37,6 +37,13 @@
3173 - link for links, attribute href gives the target
3174 - img for images, attributes src, width, height an optionally href and alt
3175 - type can be used to control plugin functionality, e.g. type=equation
3176+ - table for tables, attributes
3177+ * aligns - comma separated values: right,left,center
3178+ * wraps - 0 for not wrapped, 1 for auto-wrapped line display
3179+ - thead for table header row
3180+ - th for table header cell
3181+ - trow for table row
3182+ - td for table data cell
3183
3184 Unlike html we respect line breaks and other whitespace as is.
3185 When rendering as html use the "white-space: pre" CSS definition to
3186@@ -128,7 +135,13 @@
3187 TAG = 'tag'
3188 ANCHOR = 'anchor'
3189
3190-BLOCK_LEVEL = (PARAGRAPH, HEADING, VERBATIM_BLOCK, BLOCK, OBJECT, IMAGE, LISTITEM)
3191+TABLE = 'table'
3192+HEADROW = 'thead'
3193+HEADDATA = 'th'
3194+TABLEROW = 'trow'
3195+TABLEDATA = 'td'
3196+
3197+BLOCK_LEVEL = (PARAGRAPH, HEADING, VERBATIM_BLOCK, BLOCK, OBJECT, IMAGE, LISTITEM, TABLE)
3198
3199
3200 _letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
3201@@ -891,6 +904,7 @@
3202 self._tail = True
3203
3204 if len(self._stack) > 1 and not (tag == 'img' or tag == 'object'
3205+ or tag == HEADDATA or tag == TABLEDATA
3206 or (self._last.text and not self._last.text.isspace())
3207 or self._last.getchildren() ):
3208 # purge empty tags
3209@@ -1146,6 +1160,10 @@
3210 if not tag or tag != self.context[-1].tag:
3211 raise AssertionError, 'Unexpected tag closed: %s' % tag
3212 _, attrib, strings = self.context.pop()
3213+
3214+ if tag in (TABLEDATA, HEADDATA) and not isinstance(strings, basestring):
3215+ strings = [''.join(strings)] # child elements of td and th are concated to string
3216+
3217 if tag in self.TAGS:
3218 assert strings, 'Can not append empty %s element' % tag
3219 start, end = self.TAGS[tag]
3220@@ -1484,3 +1502,151 @@
3221 self.attrib = None
3222 if content:
3223 self.extend(content)
3224+
3225+
3226+class TableParser():
3227+ '''Common functions for converting a table from its' xml structure to another format'''
3228+
3229+ @staticmethod
3230+ def width2dim(lines):
3231+ '''
3232+ Calculates the characters on each column and return list of widths
3233+ :param lines: 2-dim multiline rows
3234+ :return: the number of characters of the longest cell-value by column
3235+ '''
3236+ widths = [max(map(len, line)) for line in zip(*lines)]
3237+ return widths
3238+
3239+ @staticmethod
3240+ def width3dim(lines):
3241+ '''
3242+ Calculates the characters on each column and return list of widths
3243+ :param lines: 3-dim multiline rows
3244+ :return: the number of characters of the longest cell-value by column
3245+ '''
3246+ lines = reduce(lambda x, y: x+y, lines)
3247+ widths = [max(map(len, line)) for line in zip(*lines)]
3248+ return widths
3249+
3250+ @staticmethod
3251+ def convert_to_multiline_cells(rows):
3252+ '''
3253+ Each cell of a list of list is splitted by "\n" and a 3-dimensional list is returned,
3254+ whereas each tuple represents a line and multiple lines represents a row and multiple rows represents the table
3255+ c11a = Cell in Row 1 in Column 1 in first = a line
3256+ :param strings: format like (('c11a \n c11b', 'c12a \n c12b'), ('c21', 'c22a \n 22b'))
3257+ :return: format like (((c11a, c12a), (c11b, c12b)), ((c21, c22a), ('', c22b)))
3258+ '''
3259+ multi_rows = [map(lambda cell: cell.split("\n"), row) for row in rows]
3260+
3261+ # grouping by line, not by row
3262+ strings = [map(lambda *line: map(lambda val: val if val is not None else '', line), *row) for row in multi_rows]
3263+ return strings
3264+
3265+ @staticmethod
3266+ def get_options(attrib):
3267+ '''
3268+ Lists the attributes as tuple
3269+ :param attrib:
3270+ :return: tuple of attributes
3271+ '''
3272+ aligns = attrib['aligns'].split(',')
3273+ wraps = map(int, attrib['wraps'].split(','))
3274+
3275+ return aligns, wraps
3276+
3277+ @staticmethod
3278+ def rowsep(maxwidths, x='+', y='-'):
3279+ '''
3280+ Displays a row separator
3281+ example: rowsep((3,0), '-', '+') -> +-----+--+
3282+ :param maxwidths: list of column lengths
3283+ :param x: point-separator
3284+ :param y: line-separator
3285+ :return: a textline
3286+ '''
3287+ return x + x.join(map(lambda width: (width+2) * y, maxwidths)) + x
3288+
3289+ @staticmethod
3290+ def headsep(maxwidths, aligns, x='|', y='-'):
3291+ '''
3292+ Displays a header separation with alignment infos
3293+ example: rowsep((3,0), '-', '+') -> +-----+--+
3294+ :param maxwidths: list of column lengths
3295+ :param aligns: list of alignments
3296+ :param x: point-separator
3297+ :param y: line-separator
3298+ :return: a textline
3299+ '''
3300+ cells = []
3301+ for width, align in zip(maxwidths, aligns):
3302+ line = width * y
3303+ if align == 'left':
3304+ cell = ':' + line + y
3305+ elif align == 'right':
3306+ cell = y + line + ':'
3307+ elif align == 'center':
3308+ cell = ':' + line + ':'
3309+ else:
3310+ cell = y + line + y
3311+ cells.append(cell)
3312+ return x + x.join(cells) + x
3313+
3314+ @staticmethod
3315+ def headline(row, maxwidths, aligns, wraps, x='|', y=' '):
3316+ '''
3317+ Displays a headerline line in text format
3318+ :param row: tuple of cells
3319+ :param maxwidths: list of column length
3320+ :param aligns: list of alignments
3321+ :param x: point-separator
3322+ :param y: space-separator
3323+ :return: a textline
3324+ '''
3325+ row = TableParser.alignrow(row, maxwidths, aligns, y)
3326+ cells = []
3327+ for val, wrap in zip(row, wraps):
3328+ if wrap == 1:
3329+ val = val[:-1]+'<'
3330+ cells.append(val)
3331+ return x + x.join(cells) + x
3332+
3333+ @staticmethod
3334+ def rowline(row, maxwidths, aligns, x='|', y=' '):
3335+ '''
3336+ Displays a normal column line in text format
3337+ example: rowline((3,0), (left, left), '+','-') -> +-aa--+--+
3338+ :param row: tuple of cells
3339+ :param maxwidths: list of column length
3340+ :param aligns: list of alignments
3341+ :param x: point-separator
3342+ :param y: space-separator
3343+ :return: a textline
3344+ '''
3345+ cells = TableParser.alignrow(row, maxwidths, aligns, y)
3346+ return x + x.join(cells) + x
3347+
3348+ @staticmethod
3349+ def alignrow(row, maxwidths, aligns, y=' '):
3350+ '''
3351+ Formats a row with the right alignments
3352+ :param row: tuple of cells
3353+ :param maxwidths: list of column length
3354+ :param aligns: list of alignments
3355+ :param x: point-separator
3356+ :param y: space-separator
3357+ :return: a textline
3358+ '''
3359+ cells = []
3360+ for val, align, maxwidth in zip(row, aligns, maxwidths):
3361+ if align == 'left':
3362+ (lspace, rspace) = (1, maxwidth - len(val) + 1)
3363+ elif align == 'right':
3364+ (lspace, rspace) = (maxwidth - len(val) + 1, 1)
3365+ elif align == 'center':
3366+ lspace = (maxwidth - len(val)) / 2 + 1
3367+ rspace = (maxwidth - lspace - len(val) + 2)
3368+ else:
3369+ (lspace, rspace) = (1, maxwidth - len(val) + 1)
3370+ cells.append(lspace * y + val + rspace * y)
3371+ return cells
3372\ No newline at end of file
3373
3374=== modified file 'zim/formats/html.py'
3375--- zim/formats/html.py 2014-11-11 20:26:12 +0000
3376+++ zim/formats/html.py 2015-04-04 11:26:41 +0000
3377@@ -220,3 +220,48 @@
3378
3379 # TODO put content in attrib, use text for caption (with full recursion)
3380 # See img
3381+
3382+ def dump_table(self, tag, attrib, strings):
3383+ aligns = attrib['aligns'].split(',')
3384+ tdcount = 0
3385+
3386+ def align(pos):
3387+ if pos == 'left' or pos == 'right' or pos == 'center':
3388+ return ' align="' + pos + '"'
3389+ return ''
3390+
3391+ for i, string in enumerate(strings):
3392+ if '<tr' in string:
3393+ tdcount = 0
3394+ elif '<th' in string:
3395+ strings[i] = string.replace('<th', '<th' + align(aligns[tdcount]))
3396+ tdcount += 1
3397+ elif '<td' in string:
3398+ strings[i] = string.replace('<td', '<td' + align(aligns[tdcount]))
3399+ tdcount += 1
3400+
3401+ strings.insert(0, '<table>\n')
3402+ strings.append('</table>\n')
3403+ return strings
3404+
3405+ def dump_thead(self, tag, attrib, strings):
3406+ strings.insert(0, '<thead><tr>\n')
3407+ strings.append('</tr></thead>\n')
3408+ return strings
3409+
3410+ def dump_th(self, tag, attrib, strings):
3411+ strings.insert(0, ' <th>')
3412+ strings.append('</th>\n')
3413+ return strings
3414+
3415+ def dump_trow(self, tag, attrib, strings):
3416+ strings.insert(0, '<tr>\n')
3417+ strings.append('</tr>\n')
3418+ return strings
3419+
3420+ def dump_td(self, tag, attrib, strings):
3421+ if strings == [" "]:
3422+ strings = ["&nbsp;"]
3423+ strings.insert(0, ' <td>')
3424+ strings.append('</td>\n')
3425+ return strings
3426
3427=== modified file 'zim/formats/latex.py'
3428--- zim/formats/latex.py 2014-08-22 20:18:49 +0000
3429+++ zim/formats/latex.py 2015-04-04 11:26:41 +0000
3430@@ -227,3 +227,27 @@
3431 assert False, 'Found no suitable delimiter for verbatim text: %s' % element
3432
3433 dump_object_fallback = dump_pre
3434+
3435+ def dump_table(self, tag, attrib, strings):
3436+ table = [] # result table
3437+ rows = strings
3438+
3439+ aligns, _wraps = TableParser.get_options(attrib)
3440+ rowline = lambda row: '&'.join([' ' + cell + ' ' for cell in row]) + '\\tabularnewline\n\hline'
3441+ aligns = map(lambda a: 'l' if a == 'left' else 'r' if a == 'right' else 'c' if a == 'center' else 'l', aligns)
3442+
3443+ for i, row in enumerate(rows):
3444+ for j, (cell, align) in enumerate(zip(row, aligns)):
3445+ if '\n' in cell:
3446+ rows[i][j] = '\shortstack[' + align + ']{' + cell.replace("\n", "\\") + '}'
3447+
3448+ # print table
3449+ table.append('\\begin{tabular}{ |' + '|'.join(aligns) + '| }')
3450+ table.append('\hline')
3451+
3452+ table += [rowline(rows[0])]
3453+ table.append('\hline')
3454+ table += [rowline(row) for row in rows[1:]]
3455+
3456+ table.append('\end{tabular}')
3457+ return map(lambda line: line+"\n", table)
3458
3459=== modified file 'zim/formats/markdown.py'
3460--- zim/formats/markdown.py 2014-05-04 19:17:46 +0000
3461+++ zim/formats/markdown.py 2015-04-04 11:26:41 +0000
3462@@ -108,4 +108,31 @@
3463 # dump object as verbatim block
3464 return self.prefix_lines('\t', strings)
3465
3466-
3467+ def dump_table(self, tag, attrib, strings):
3468+ table = [] # result table
3469+ rows = strings
3470+
3471+ aligns, _wraps = TableParser.get_options(attrib)
3472+ maxwidths = TableParser.width2dim(rows)
3473+ headsep = TableParser.headsep(maxwidths, aligns, x='|', y='-')
3474+ rowline = lambda row: TableParser.rowline(row, maxwidths, aligns)
3475+
3476+ # print table
3477+ table += [rowline(rows[0])]
3478+ table.append(headsep)
3479+ table += [rowline(row) for row in rows[1:]]
3480+ return map(lambda line: line+"\n", table)
3481+
3482+ def dump_thead(self, tag, attrib, strings):
3483+ return [strings]
3484+
3485+ def dump_th(self, tag, attrib, strings):
3486+ strings = [s.replace('\n', '<br>').replace('|', '∣') for s in strings]
3487+ return strings
3488+
3489+ def dump_trow(self, tag, attrib, strings):
3490+ return [strings]
3491+
3492+ def dump_td(self, tag, attrib, strings):
3493+ strings = [s.replace('\n', '<br>').replace('|', '∣') for s in strings]
3494+ return strings
3495
3496=== modified file 'zim/formats/plain.py'
3497--- zim/formats/plain.py 2014-05-04 19:17:46 +0000
3498+++ zim/formats/plain.py 2015-04-04 11:26:41 +0000
3499@@ -188,3 +188,36 @@
3500
3501 def dump_object_fallback(self, tag, attrib, strings):
3502 return strings
3503+
3504+ def dump_table(self, tag, attrib, strings):
3505+ table = [] # result table
3506+
3507+ aligns, _wraps = TableParser.get_options(attrib)
3508+ rows = TableParser.convert_to_multiline_cells(strings)
3509+ maxwidths = TableParser.width3dim(rows)
3510+ rowsep = lambda y: TableParser.rowsep(maxwidths, x='+', y=y)
3511+ rowline = lambda row: TableParser.rowline(row, maxwidths, aligns)
3512+
3513+ # print table
3514+ table.append(rowsep('-'))
3515+ table += [rowline(line) for line in rows[0]]
3516+ table.append(rowsep('='))
3517+ for row in rows[1:]:
3518+ table += [rowline(line) for line in row]
3519+ table.append(rowsep('-'))
3520+
3521+ return map(lambda line: line+"\n", table)
3522+
3523+ def dump_thead(self, tag, attrib, strings):
3524+ return [strings]
3525+
3526+ def dump_th(self, tag, attrib, strings):
3527+ strings = [s.replace('|', '∣') for s in strings]
3528+ return strings
3529+
3530+ def dump_trow(self, tag, attrib, strings):
3531+ return [strings]
3532+
3533+ def dump_td(self, tag, attrib, strings):
3534+ strings = [s.replace('|', '∣') for s in strings]
3535+ return strings
3536
3537=== modified file 'zim/formats/rst.py'
3538--- zim/formats/rst.py 2014-05-04 19:17:46 +0000
3539+++ zim/formats/rst.py 2015-04-04 11:26:41 +0000
3540@@ -93,3 +93,36 @@
3541 # can be done using "figure" directive
3542
3543 dump_object_fallback = dump_pre
3544+
3545+ def dump_table(self, tag, attrib, strings):
3546+ table = [] # result table
3547+
3548+ aligns, _wraps = TableParser.get_options(attrib)
3549+ rows = TableParser.convert_to_multiline_cells(strings)
3550+ maxwidths = TableParser.width3dim(rows)
3551+ rowsep = lambda y: TableParser.rowsep(maxwidths, x='+', y=y)
3552+ rowline = lambda row: TableParser.rowline(row, maxwidths, aligns)
3553+
3554+ # print table
3555+ table.append(rowsep('-'))
3556+ table += [rowline(line) for line in rows[0]]
3557+ table.append(rowsep('='))
3558+ for row in rows[1:]:
3559+ table += [rowline(line) for line in row]
3560+ table.append(rowsep('-'))
3561+
3562+ return map(lambda line: line+"\n", table)
3563+
3564+ def dump_thead(self, tag, attrib, strings):
3565+ return [strings]
3566+
3567+ def dump_th(self, tag, attrib, strings):
3568+ strings = [s.replace('|', '∣') for s in strings]
3569+ return strings
3570+
3571+ def dump_trow(self, tag, attrib, strings):
3572+ return [strings]
3573+
3574+ def dump_td(self, tag, attrib, strings):
3575+ strings = [s.replace('|', '∣') for s in strings]
3576+ return strings
3577
3578=== modified file 'zim/formats/wiki.py'
3579--- zim/formats/wiki.py 2014-11-09 11:07:20 +0000
3580+++ zim/formats/wiki.py 2015-04-04 11:26:41 +0000
3581@@ -163,6 +163,15 @@
3582 r'^( ==+ [\ \t]+ \S.*? ) [\ \t]* =* \n', # "==== heading ===="
3583 process=self.parse_heading
3584 ),
3585+ # standard table format
3586+ Rule(TABLE, r'''
3587+ ^(\|.*\|)$\n # starting and ending with |
3588+ ^( (?:\| [ \|\-:]+ \|$\n)? ) # column align
3589+ ( (?:^\|.*\|$\n)+ ) # multi-lines: starting and ending with |
3590+ ^(?= \s*? \n) # empty line / only spaces
3591+ ''',
3592+ process=self.parse_table
3593+ )
3594 )
3595 p.process_unmatched = self.parse_para
3596 return p
3597@@ -195,8 +204,7 @@
3598
3599 builder.append(VERBATIM_BLOCK, attrib, text)
3600
3601- @staticmethod
3602- def parse_object(builder, indent, header, body):
3603+ def parse_object(self, builder, indent, header, body):
3604 '''Custom object'''
3605 type, param = header.split(':', 1)
3606 type = type.strip().lower()
3607@@ -216,9 +224,91 @@
3608 if indent:
3609 body = _remove_indent(body, indent)
3610 attrib['indent'] = len(indent)
3611-
3612 builder.append(OBJECT, attrib, body)
3613
3614+ def check_multi_attribute(self, attrib, key, default, list_length):
3615+ '''
3616+ Correct multi-attributes, so they do fit with column length of table
3617+ :param attrib: key-value store
3618+ :param key: key to select of attribute
3619+ :param default: default value for one list entry
3620+ :param list_length: required length of selected attribute
3621+ :return: attribute-value as list of different options
3622+ '''
3623+ if attrib and key in attrib and attrib[key]:
3624+ values = attrib[key].split(',')
3625+ else:
3626+ values = []
3627+
3628+ while len(values) > list_length:
3629+ values.pop()
3630+ while len(values) < list_length:
3631+ values.append(default)
3632+ return ','.join(values)
3633+
3634+ def parse_table(self, builder, headerrow, alignstyle, body):
3635+ '''Table parsing'''
3636+ body = body.replace('\\|', '#124;') # escaping
3637+ rows = body.split('\n')[:-1]
3638+ # get maximum number of columns - each columns must have same size
3639+ nrcols = max([headerrow.count('|')]+[row.count('|') for row in rows])-1
3640+
3641+ # transform header separator line into alignment definitions
3642+ aligns = []
3643+ while alignstyle.count('|') < nrcols+1:
3644+ alignstyle += '|' # fill cells thus they match with nr of headers
3645+ for celltext in alignstyle.split('|')[1:-1]:
3646+ celltext = celltext.strip()
3647+ if celltext.startswith(':') and celltext.endswith(':'):
3648+ alignment = 'center'
3649+ elif celltext.startswith(':'):
3650+ alignment = 'left'
3651+ elif celltext.endswith(':'):
3652+ alignment = 'right'
3653+ else:
3654+ alignment = 'normal'
3655+ aligns.append(alignment)
3656+
3657+ # collect wrap settings from first table row
3658+ headers = []
3659+ wraps = []
3660+ for celltext in headerrow.split('|')[1:-1]:
3661+ if celltext.rstrip().endswith('<'):
3662+ celltext = celltext.rstrip().rstrip('<')
3663+ wraps.append(1)
3664+ else:
3665+ wraps.append(0)
3666+ headers.append(celltext)
3667+
3668+ attrib = {'aligns': ','.join(aligns), 'wraps': ','.join(map(str, wraps))}
3669+
3670+
3671+ # build table
3672+ builder.start(TABLE, attrib)
3673+
3674+ builder.start(HEADROW)
3675+ for celltext in headers:
3676+ celltext = celltext.replace('#124;', '|').replace('\\n', '\n').strip()
3677+ if not celltext:
3678+ celltext = ' ' # celltext must contain at least one character
3679+ builder.append(HEADDATA, {}, celltext)
3680+ builder.end(HEADROW)
3681+
3682+ for bodyrow in rows:
3683+ while bodyrow.count('|') < nrcols+1:
3684+ bodyrow += '|' # fill cells thus they match with nr of headers
3685+ builder.start(TABLEROW)
3686+ for celltext in bodyrow.split('|')[1:-1]:
3687+ builder.start(TABLEDATA)
3688+ celltext = celltext.replace('#124;', '|').replace('\\n', '\n').strip() # cleanup cell
3689+ if not celltext:
3690+ celltext = ' ' # celltext must contain at least one character
3691+ self.inline_parser(builder, celltext)
3692+ builder.end(TABLEDATA)
3693+ builder.end(TABLEROW)
3694+
3695+ builder.end(TABLE)
3696+
3697 def parse_para(self, builder, text):
3698 '''Split a text into paragraphs and empty lines'''
3699 if text.isspace():
3700@@ -454,3 +544,35 @@
3701
3702 # TODO put content in attrib, use text for caption (with full recursion)
3703 # See img
3704+
3705+ def dump_table(self, tag, attrib, strings):
3706+ # logger.debug("Dumping table: %s, %s", attrib, strings)
3707+
3708+ table = [] # result table
3709+ rows = strings
3710+
3711+ aligns, wraps = TableParser.get_options(attrib)
3712+ maxwidths = TableParser.width2dim(rows)
3713+ headsep = TableParser.headsep(maxwidths, aligns, x='|', y='-')
3714+ rowline = lambda row: TableParser.rowline(row, maxwidths, aligns)
3715+
3716+ # print table
3717+ table += [TableParser.headline(rows[0], maxwidths, aligns, wraps)]
3718+ table.append(headsep)
3719+ table += [rowline(row) for row in rows[1:]]
3720+ return map(lambda line: line+"\n", table)
3721+
3722+ def dump_thead(self, tag, attrib, strings):
3723+ return [strings]
3724+
3725+ def dump_th(self, tag, attrib, strings):
3726+ strings = map(lambda s: s.replace('\n', '\\n').replace('|', '\\|'), strings)
3727+ return strings
3728+
3729+ def dump_trow(self, tag, attrib, strings):
3730+ return [strings]
3731+
3732+ def dump_td(self, tag, attrib, strings):
3733+ strings = map(lambda s: s.replace('\n', '\\n').replace('|', '\\|'), strings)
3734+ strings = map(lambda s: s.replace('<br>', '\\n'), strings)
3735+ return strings
3736
3737=== modified file 'zim/gui/objectmanager.py'
3738--- zim/gui/objectmanager.py 2014-11-09 11:07:20 +0000
3739+++ zim/gui/objectmanager.py 2015-04-04 11:26:41 +0000
3740@@ -145,7 +145,7 @@
3741 self.vbox.pack_start(label)
3742
3743 def _add_load_plugin_bar(self, plugin):
3744- key, name, activatable, klass = plugin
3745+ key, name, activatable, klass, _winextension = plugin
3746
3747 hbox = gtk.HBox(False, 5)
3748 label = gtk.Label(_("Plugin %s is required to display this object.") % name)
3749
3750=== modified file 'zim/gui/pageview.py'
3751--- zim/gui/pageview.py 2014-11-11 20:26:12 +0000
3752+++ zim/gui/pageview.py 2015-04-04 11:26:41 +0000
3753@@ -43,9 +43,12 @@
3754 from zim.gui.applications import OpenWithMenu
3755 from zim.gui.clipboard import Clipboard, SelectionClipboard, \
3756 PARSETREE_ACCEPT_TARGETS, parsetree_from_selectiondata
3757-from zim.objectmanager import ObjectManager, CustomObjectClass
3758+from zim.objectmanager import ObjectManager, CustomObjectClass, FallbackObject
3759 from zim.gui.objectmanager import CustomObjectWidget, POSITION_BEGIN, POSITION_END
3760 from zim.utils import WeakSet
3761+from zim.formats import get_dumper
3762+from zim.formats.wiki import Dumper as WikiDumper
3763+from zim.plugins import PluginManager
3764
3765
3766 logger = logging.getLogger('zim.gui.pageview')
3767@@ -515,6 +518,9 @@
3768 @ivar user_action: A L{UserActionContext} context manager
3769 @ivar finder: A L{TextFinder} for this buffer
3770
3771+ @signal: C{reload-page ()}:
3772+ Emitted when plugin is activated and current page should be reloaded
3773+ to display object properly
3774 @signal: C{begin-insert-tree ()}:
3775 Emitted at the begin of a complex insert
3776 @signal: C{end-insert-tree ()}:
3777@@ -523,6 +529,8 @@
3778 Gives inserted tree after inserting it
3779 @signal: C{textstyle-changed (style)}:
3780 Emitted when textstyle at the cursor changes
3781+ @signal: C{link-clicked ()}:
3782+ Emitted when a link is clicked; for example within a table cell
3783 @signal: C{clear ()}:
3784 emitted to clear the whole buffer before destruction
3785 @signal: C{undo-save-cursor (iter)}:
3786@@ -530,6 +538,12 @@
3787 lock the current cursor position
3788 @signal: C{insert-object (object_element)}: request inserting of
3789 custom object
3790+ @signal: C{edit-object (object_element)}: request editing of
3791+ custom object
3792+ @signal: C{insert-table (table_element)}: request inserting of
3793+ table object
3794+ @signal: C{edit-table (table_element)}: request editing of
3795+ table object
3796
3797 @todo: document tag styles that are supported
3798 '''
3799@@ -551,6 +565,11 @@
3800 'clear': (gobject.SIGNAL_RUN_LAST, None, ()),
3801 'undo-save-cursor': (gobject.SIGNAL_RUN_LAST, None, (object,)),
3802 'insert-object': (gobject.SIGNAL_RUN_LAST, None, (object,)),
3803+ 'edit-object': (gobject.SIGNAL_RUN_LAST, None, (object,)),
3804+ 'insert-table': (gobject.SIGNAL_RUN_LAST, None, (object,)),
3805+ 'edit-table': (gobject.SIGNAL_RUN_LAST, None, (object,)),
3806+ 'link-clicked': (gobject.SIGNAL_RUN_LAST, None, (object,)),
3807+ 'reload-page': (gobject.SIGNAL_RUN_LAST, None, (object,)),
3808 }
3809
3810 # style attributes
3811@@ -919,6 +938,8 @@
3812 self.insert_at_cursor(element.text)
3813 self.set_textstyle(None)
3814 set_indent(None)
3815+ elif element.tag == 'table':
3816+ self.emit('insert-table', element)
3817 elif element.tag == 'object':
3818 if 'indent' in element.attrib:
3819 set_indent(int(element.attrib['indent']))
3820@@ -2323,11 +2344,16 @@
3821 continue
3822 if hasattr(anchor, 'manager'):
3823 attrib = anchor.manager.get_attrib()
3824- data = anchor.manager.get_data()
3825- logger.debug("Anchor with CustomObject: %s", anchor.manager)
3826- builder.start('object', attrib)
3827- builder.data(data)
3828- builder.end('object')
3829+ if attrib and attrib['type'] == 'table':
3830+ self.build_parsetree_of_table(builder, anchor.manager, iter)
3831+ else:
3832+ # general object related parsing
3833+ data = anchor.manager.get_data()
3834+ logger.debug("Anchor with CustomObject: %s", anchor.manager)
3835+ builder.start('object', attrib)
3836+ builder.data(data)
3837+ builder.end('object')
3838+
3839 anchor.manager.set_modified(False)
3840 iter.forward_char()
3841 else:
3842@@ -2413,6 +2439,55 @@
3843
3844 return tree
3845
3846+ def build_parsetree_of_table(self, builder, anchormanager, iter):
3847+ logger.debug("Anchor with TableObject: %s", anchormanager)
3848+ attrib = anchormanager.get_attrib()
3849+ del attrib['type']
3850+ tabledata = anchormanager.get_data()
3851+
3852+ # inserts a newline before and after table-object
3853+ bound = iter.copy()
3854+ bound.backward_char()
3855+ char_before_table = bound.get_slice(iter)
3856+ need_newline_infront = char_before_table.decode('utf-8') != "\n".decode('utf-8')
3857+ bound = iter.copy()
3858+ bound.forward_char()
3859+ iter2 = bound.copy()
3860+ bound.forward_char()
3861+ char_after_table = iter2.get_slice(bound)
3862+ need_newline_behind = char_after_table.decode('utf-8') != "\n".decode('utf-8')
3863+
3864+ # table-editor plugin is not activated -> handle table-object like a fallback-object
3865+ if isinstance(tabledata, basestring):
3866+ if need_newline_infront:
3867+ builder.data('\n')
3868+ builder.data(tabledata)
3869+ if need_newline_behind:
3870+ builder.data('\n')
3871+ return
3872+
3873+ headers, rows, attrib = tabledata
3874+ if need_newline_infront:
3875+ builder.data('\n')
3876+
3877+ builder.start('table', attrib)
3878+ builder.start('thead')
3879+ for header in headers:
3880+ builder.start('th')
3881+ builder.data(header)
3882+ builder.end('th')
3883+ builder.end('thead')
3884+ for row in rows:
3885+ builder.start('trow')
3886+ for cell in row:
3887+ builder.start('td')
3888+ builder.data(cell)
3889+ builder.end('td')
3890+ builder.end('trow')
3891+ builder.end('table')
3892+ if need_newline_behind:
3893+ builder.data('\n')
3894+
3895 def select_line(self):
3896 '''Selects the current line
3897
3898@@ -3290,6 +3365,14 @@
3899 start = self.buffer.get_iter_at_line(line)
3900 if start.ends_line():
3901 continue
3902+
3903+ # search within external widgets
3904+ if start.get_child_anchor() is not None:
3905+ result = self._search_in_widget(start, step)
3906+ if result:
3907+ yield (start, start, result[2])
3908+ else:
3909+ continue
3910 end = start.copy()
3911 end.forward_to_line_end()
3912 text = start.get_slice(end)
3913@@ -3304,6 +3387,34 @@
3914 line, match.end() )
3915 yield startiter, enditer, match
3916
3917+
3918+ def _search_in_widget(self, start, step):
3919+ '''
3920+ Search within a widget
3921+ :param start: position-of-widget
3922+ :param step: search direction (up / down): -1 / 1
3923+ :return: tuple (startiter, enditer, match)
3924+ '''
3925+ if start.get_child_anchor() is None or len(start.get_child_anchor().get_widgets()) < 1:
3926+ return
3927+ widgets = start.get_child_anchor().get_widgets()
3928+ if isinstance(widgets[0], zim.plugins.tableeditor.TableViewWidget):
3929+ table = widgets[0]
3930+ liststore = table.get_liststore()
3931+ iter = liststore.get_iter_root()
3932+ while iter is not None:
3933+ for col in range(liststore.get_n_columns()):
3934+ text = liststore.get_value(iter, col)
3935+ matches = self.regex.finditer(text)
3936+ if step == -1:
3937+ matches = list(matches)
3938+ matches.reverse()
3939+ for match in matches:
3940+ startiter = iter
3941+ enditer = iter
3942+ return startiter, enditer, match
3943+ iter = liststore.iter_next(iter)
3944+
3945 def replace(self, string):
3946 '''Replace current match
3947
3948@@ -3326,11 +3437,14 @@
3949 string = match.expand(string)
3950
3951 offset = start.get_offset()
3952- with self.buffer.user_action:
3953- self.buffer.select_range(start, end) # ensure editmode logic is used
3954- self.buffer.delete(start, end)
3955- self.buffer.insert_at_cursor(string)
3956
3957+ if start.get_child_anchor() is not None:
3958+ self._replace_in_widget(start, self.regex, string) # replace within external widgets
3959+ else:
3960+ with self.buffer.user_action:
3961+ self.buffer.select_range(start, end) # ensure editmode logic is used
3962+ self.buffer.delete(start, end)
3963+ self.buffer.insert_at_cursor(string)
3964 start = self.buffer.get_iter_at_offset(offset)
3965 end = self.buffer.get_iter_at_offset(offset+len(string))
3966 self.buffer.select_range(start, end)
3967@@ -3341,6 +3455,36 @@
3968
3969 self._update_highlight()
3970
3971+ '''
3972+ Replace within a widget
3973+ :param start: position-of-widget
3974+ :param regex: regular expression pattern
3975+ :param text: substituation text
3976+ :param replaceall: boolean if all matches should be replaced
3977+ :return: True / False - a replacement was done / no replaces
3978+ '''
3979+ def _replace_in_widget(self, start, regex, string, replaceall=False):
3980+ if start.get_child_anchor() is None or len(start.get_child_anchor().get_widgets()) < 1:
3981+ return
3982+ widgets = start.get_child_anchor().get_widgets()
3983+ if isinstance(widgets[0], zim.plugins.tableeditor.TableViewWidget):
3984+ table = widgets[0]
3985+ liststore = table.get_liststore()
3986+ iter = liststore.get_iter_root()
3987+ has_replaced = False
3988+ while iter is not None:
3989+ for col in range(liststore.get_n_columns()):
3990+ text = liststore.get_value(iter, col)
3991+ if(regex.search(text)):
3992+ newtext = regex.sub(string, text)
3993+ liststore.set_value(iter, col, newtext)
3994+ if(not replaceall):
3995+ return True
3996+ else:
3997+ has_replaced = True
3998+ iter = liststore.iter_next(iter)
3999+ return has_replaced
4000+
4001 def replace_all(self, string):
4002 '''Replace all matched
4003
4004@@ -3366,9 +3510,12 @@
4005 for start, end, string in matches:
4006 start = self.buffer.get_iter_at_offset(start)
4007 end = self.buffer.get_iter_at_offset(end)
4008- self.buffer.select_range(start, end) # ensure editmode logic is used
4009- self.buffer.delete(start, end)
4010- self.buffer.insert(start, string)
4011+ if start.get_child_anchor() is not None:
4012+ self._replace_in_widget(start, self.regex, string, True)
4013+ else:
4014+ self.buffer.select_range(start, end) # ensure editmode logic is used
4015+ self.buffer.delete(start, end)
4016+ self.buffer.insert(start, string)
4017
4018 self._update_highlight()
4019
4020@@ -4875,6 +5022,7 @@
4021 self.page = page
4022 buffer = TextBuffer(self.ui.notebook, self.page)
4023 buffer.connect('insert-object', self.on_insert_object)
4024+ buffer.connect('insert-table', self.on_insert_table)
4025 self.view.set_buffer(buffer)
4026 tree = page.get_parsetree()
4027
4028@@ -5428,6 +5576,9 @@
4029
4030 menu.show_all()
4031
4032+ def do_reload_page(self):
4033+ self.ui.reload_page()
4034+
4035 def undo(self):
4036 '''Menu action to undo a single step'''
4037 self.undostack.undo()
4038@@ -5909,6 +6060,75 @@
4039
4040 widget.show_all()
4041
4042+ def insert_table_at_cursor(self, obj):
4043+ '''Inserts a table object in the page
4044+ @param obj: an object implementing L{CustomerObjectClass}
4045+ '''
4046+ assert isinstance(obj, CustomObjectClass)
4047+ self.on_insert_table(self.view.get_buffer(), obj)
4048+
4049+ def on_insert_table(self, buffer, obj, interactive=False):
4050+ # adopted from on_insert_object, with some changes:
4051+ # - 'type=table' added to xml-element
4052+ # - content of fallback-object is like a plaintext table
4053+ # - content of table-widget is not text, but a xml table tree
4054+ # - callbacks for 'link-clicked' and 'edit-object'
4055+ logger.debug("Insert table(%s, %s)", buffer, obj)
4056+
4057+ obj.attrib['type'] = 'table'
4058+ if not isinstance(obj, CustomObjectClass):
4059+ # assume obj is a parsetree element
4060+ element = obj
4061+ if not 'type' in element.attrib:
4062+ return None
4063+ obj = ObjectManager.get_object(element.attrib['type'], element.attrib, element)
4064+ if not hasattr(obj, 'attr'):
4065+ obj.attr = dict()
4066+ obj.attr['type'] = 'table'
4067+
4068+ if isinstance(obj, FallbackObject):
4069+ # if table plugin is not loaded - show table as plain text
4070+ tree = ParseTree(element)
4071+ text = get_dumper('wiki').dump(tree)
4072+ lines = "".join(text)
4073+ obj._data = lines
4074+
4075+ def on_modified_changed(obj):
4076+ if obj.get_modified() and not buffer.get_modified():
4077+ buffer.set_modified(True)
4078+
4079+ def on_edit_object(pageview, obj):
4080+ plugin = ObjectManager.find_plugin(obj._attrib['type'])
4081+ window_extension = plugin[4]
4082+
4083+ if window_extension:
4084+ window_extension.do_edit_object(obj)
4085+
4086+ obj.connect('modified-changed', on_modified_changed)
4087+ obj.connect_object('link-clicked', PageView.do_link_clicked, self)
4088+ obj.connect_object('edit-object', on_edit_object, self)
4089+
4090+ iter = buffer.get_insert_iter()
4091+
4092+ def on_release_cursor(widget, position, anchor):
4093+ myiter = buffer.get_iter_at_child_anchor(anchor)
4094+ if position == POSITION_END:
4095+ myiter.forward_char()
4096+ buffer.place_cursor(myiter)
4097+ self.view.grab_focus()
4098+
4099+ anchor = ObjectAnchor(obj)
4100+ buffer.insert_child_anchor(iter, anchor)
4101+ widget = obj.get_widget()
4102+ assert isinstance(widget, CustomObjectWidget)
4103+ widget.connect('release-cursor', on_release_cursor, anchor)
4104+ self.view.add_child_at_anchor(widget, anchor)
4105+ self._object_widgets.add(widget)
4106+
4107+ widget.show_all()
4108+ if not isinstance(obj, FallbackObject):
4109+ widget.toolbar_hide()
4110+
4111 def on_view_size_allocate(self, textview, allocation):
4112 for widget in self._object_widgets:
4113 if widget._resize:
4114
4115=== modified file 'zim/objectmanager.py'
4116--- zim/objectmanager.py 2014-11-09 11:07:20 +0000
4117+++ zim/objectmanager.py 2015-04-04 11:26:41 +0000
4118@@ -23,11 +23,13 @@
4119 def __init__(self):
4120 self.factories = {}
4121 self.objects = {'fallback': WeakSet()}
4122+ self.window_extensions = {}
4123
4124- def register_object(self, type, factory):
4125+ def register_object(self, type, factory, window_extension=None):
4126 '''Register a factory method or class for a specific object type.
4127 @param type: the object type as string (unique name)
4128 @param factory: can be either an object class or a method,
4129+ @param window_extension: dictionary - the plugin related window_extension
4130 should callable and return objects. When constructing objects
4131 this factory will be called as::
4132
4133@@ -44,6 +46,7 @@
4134 old = self.factories.get(type)
4135 self.factories[type] = factory
4136 self.objects[type] = WeakSet()
4137+ self.window_extensions[type] = window_extension
4138 return old
4139
4140 def unregister_object(self, type):
4141@@ -97,8 +100,8 @@
4142 '''Find a plugin to handle a specific object type. Intended to
4143 suggest plugins to the user that can be loaded.
4144 @param type: object type as string
4145- @returns: a 3-tuple of the plugin name, a boolean for the
4146- dependency check, and the plugin class, or C{None}.
4147+ @returns: a 5-tuple of the plugin name, a boolean for the
4148+ dependency check, the plugin class, or C{None} and the related plugin window_extension
4149 '''
4150 for name in zim.plugins.PluginManager.list_installed_plugins(): # XXX
4151 try:
4152@@ -106,7 +109,8 @@
4153 types = klass.plugin_info.get('object_types')
4154 if types and type in types:
4155 activatable = klass.check_dependencies_ok()
4156- return (name, klass.plugin_info['name'], activatable, klass)
4157+ win_ext = self.window_extensions[type] if type in self.window_extensions else None
4158+ return (name, klass.plugin_info['name'], activatable, klass, win_ext)
4159 except:
4160 logger.exception('Could not load plugin %s', name)
4161 continue
4162
4163=== added file 'zim/plugins/tableeditor.py'
4164--- zim/plugins/tableeditor.py 1970-01-01 00:00:00 +0000
4165+++ zim/plugins/tableeditor.py 2015-04-04 11:26:41 +0000
4166@@ -0,0 +1,1159 @@
4167+# -*- coding: utf-8 -*-
4168+
4169+# Author: Tobias Haupenthal
4170+# Plugin created: 2015
4171+#
4172+#
4173+# This plugin includes a whole featureset. Please be familiar with the documentation in 'Plugins:Table Editor',
4174+# before you do here any code change.
4175+#
4176+# - Suggestions for the future:
4177+# better column sort-algorithm "sort-by-number-or-string"
4178+# undo / redo - not trivial, because currently only position in textview is saved
4179+# ideas: save everytime the whole table OR save a tuple (position in textview, row, column)
4180+
4181+
4182+import gobject
4183+import gtk
4184+import logging
4185+from xml.etree import ElementTree
4186+import re
4187+import pango
4188+
4189+logger = logging.getLogger('zim.plugin.tableeditor')
4190+
4191+from zim.actions import action
4192+from zim.plugins import PluginClass, extends, WindowExtension
4193+from zim.utils import WeakSet
4194+from zim.objectmanager import ObjectManager, CustomObjectClass
4195+from zim.config import String
4196+from zim.main import get_zim_application
4197+from zim.gui.widgets import Dialog, ScrolledWindow, IconButton, InputEntry
4198+from zim.gui.objectmanager import CustomObjectWidget
4199+
4200+OBJECT_TYPE = 'table'
4201+
4202+SYNTAX_CELL_INPUT = [
4203+ ('&amp;', '&'), ('&gt;', '>'), ('&lt;', '<'), ('&quot;', '"'), ('&apos;', "'"), ('\n', '\\n')
4204+]
4205+
4206+# Regex replacement strings: Wiki-Parsetree -> Pango (Table cell) -> Input (Table cell editing)
4207+# the target pattern is easier to read, the source pattern is generated out of it
4208+# With this syntax text can be format within a table-cell
4209+SYNTAX_WIKI_PANGO2 = [
4210+ (r'<strong>\1</strong>', r'<b>\1</b>', r'**\1**'),
4211+ (r'<mark>\1</mark>', r'<span background="yellow">\1</span>', r'__\1__'),
4212+ (r'<code>\1</code>', r'<tt>\1</tt>', r"''\1''"),
4213+ (r'<strike>\1</strike>', r'<s>\1</s>', r'~~\1~~'),
4214+ # Link url without link text - Link url has always size = 0
4215+ (r'<link href="\1">\1</link>', r'<span foreground="blue">\1<span size="0">\1</span></span>', r'[[\1]]'),
4216+ # Link url with link text - Link url has always size = 0
4217+ (r'<link href="\1">\2</link>', r'<span foreground="blue">\2<span size="0">\1</span></span>', r'[[\2|\1]]'),
4218+ (r'<emphasis>\1</emphasis>', r'<i>\1</i>', r'//\1//')
4219+]
4220+
4221+# Possible alignments in edit-table-dialog
4222+COLUMNS_ALIGNMENTS = {'left': ['left', gtk.STOCK_JUSTIFY_LEFT, _('Left')], # T: alignment option
4223+ 'center': ['center', gtk.STOCK_JUSTIFY_CENTER, _('Center')], # T: alignment option
4224+ 'right': ['right', gtk.STOCK_JUSTIFY_RIGHT, _('Right')], # T: alignment option
4225+ 'normal': ['normal', None, _('Unspecified')],} # T: alignment option
4226+
4227+
4228+def reg_replace(string):
4229+ '''
4230+ Target pattern is translated into source regex pattern
4231+ :param string: target pattern
4232+ :return:source pattern
4233+ '''
4234+ string = string.replace('*', '\*').replace('[', '\[').replace(']', '\]') \
4235+ .replace(r'\1', '(.+?)', 1).replace(r'\2', '(.+?)', 1).replace('|', '\|')
4236+ return re.compile(string)
4237+
4238+# Regex compiled search patterns
4239+SYNTAX_WIKI_PANGO = [tuple(map(reg_replace, expr_list)) for expr_list in SYNTAX_WIKI_PANGO2]
4240+
4241+
4242+class TableEditorPlugin(PluginClass):
4243+ '''
4244+ This is the plugin for displaying tables within the wiki.
4245+ A table consists always of a header with at least one header-cell and at least one or several rows.
4246+ The number of cells in a row must be equal to the header.
4247+ Currently there are two attributes, which have a tuple format, so they can describe all columns:
4248+ - aligns: left, center, right
4249+ - wraps: 0 / display text in a row 1 / long text will be broken and wrapped
4250+
4251+ Most other files which are linked to this plugin are:
4252+ - zim.gui.pageview
4253+ - zim.formats.wiki
4254+ '''
4255+ plugin_info = {
4256+ 'name': _('Table Editor'), # T: plugin name
4257+ 'description': _('''\
4258+With this plugin you can embed a 'Table' into the wiki page. Tables will be shown as GTK TreeView widgets.
4259+Exporting them to various formats (i.e. HTML/LaTeX) completes the feature set.
4260+'''), # T: plugin description
4261+ 'object_types': (OBJECT_TYPE, ),
4262+ 'help': 'Plugins:Table Editor',
4263+ 'author': 'Tobias Haupenthal',
4264+ }
4265+
4266+ global LINES_NONE, LINES_HORIZONTAL, LINES_VERTICAL, LINES_BOTH # Hack - to make sure translation is loaded
4267+ LINES_BOTH = _('with lines') # T: option value
4268+ LINES_NONE = _('no grid lines') # T: option value
4269+ LINES_HORIZONTAL = _('horizontal lines') # T: option value
4270+ LINES_VERTICAL = _('vertical lines') # T: option value
4271+
4272+
4273+
4274+ plugin_preferences = (
4275+ # key, type, label, default
4276+ ('show_helper_toolbar', 'bool', _('Show helper toolbar'), True), # T: preference description
4277+
4278+ # option for displaying grid-lines within the table
4279+ ('grid_lines', 'choice', _('Grid lines'), LINES_BOTH, (LINES_BOTH, LINES_NONE, LINES_HORIZONTAL, LINES_VERTICAL)),
4280+ # T: preference description
4281+ )
4282+
4283+ def __init__(self, config=None):
4284+ ''' Constructor '''
4285+ PluginClass.__init__(self, config)
4286+ self.connectto(self.preferences, 'changed', self.on_preferences_changed)
4287+
4288+ def create_table(self, attrib, text):
4289+ '''
4290+ Automatic way for displaying the table-object as a table within the wiki,
4291+ :param attrib: {type: 'table', wraps:'1,0,1' , aligns:'left,right,center' }
4292+ :param text: XML - formated as a zim-tree table-object OR tuple of [header], [row1], [row2]
4293+ :return: a TableViewObject
4294+ '''
4295+ if ElementTree.iselement(text) and attrib.get('type') == 'table':
4296+ (header, rows) = self._tabledom_to_list(text)
4297+ else:
4298+ # parameters in case of the Table-Insert-Dialog
4299+ header = text[0]
4300+ rows = [len(text[0]) * [' ']]
4301+
4302+ '''Factory method for Table objects'''
4303+ obj = TableViewObject(attrib, header, rows, self.preferences)
4304+ return obj
4305+
4306+ def _tabledom_to_list(self, tabledata):
4307+ '''
4308+ Extracts necessary data out of a xml-table into a list structure
4309+
4310+ :param tabledata: XML - formated as a zim-tree table-object
4311+ :return: tuple of header-list and list of row lists - ([h1,h2],[[r11,r12],[r21,r22])
4312+ '''
4313+ header = map(lambda head: head.text.decode('utf-8'), tabledata.findall('thead/th'))
4314+ header = map(CellFormatReplacer.zim_to_cell, header)
4315+
4316+ rows = []
4317+ for trow in tabledata.findall('trow'):
4318+ row = trow.findall('td')
4319+ row = [ElementTree.tostring(r, 'utf-8').replace('<td>', '').replace('</td>', '') for r in row]
4320+ row = map(CellFormatReplacer.zim_to_cell, row)
4321+ rows.append(row)
4322+ return header, rows
4323+
4324+ def on_preferences_changed(self, preferences):
4325+ '''Update preferences on open table objects'''
4326+ for obj in ObjectManager.get_active_objects(OBJECT_TYPE):
4327+ obj.preferences_changed()
4328+
4329+
4330+class CellFormatReplacer:
4331+ '''
4332+ Static class for converting formated text from one into the other format:
4333+ - cell: in a wiki pageview the table-cell must be of this format
4334+ - input: if a user is editing the cell, this format is used
4335+ - zimtree: Format for zimtree xml structure
4336+ '''
4337+ @staticmethod
4338+ def cell_to_input(text, with_pango=False):
4339+ ''' Displayed table-cell will converted to gtk-entry input text '''
4340+ if with_pango:
4341+ for pattern, replace in zip(SYNTAX_WIKI_PANGO, SYNTAX_WIKI_PANGO2):
4342+ text = pattern[1].sub(replace[2], text)
4343+ for k, v in SYNTAX_CELL_INPUT:
4344+ text = text.replace(k, v)
4345+ return text
4346+
4347+ @staticmethod
4348+ def input_to_cell(text, with_pango=False):
4349+ for k, v in SYNTAX_CELL_INPUT:
4350+ text = text.replace(v, k)
4351+ if with_pango:
4352+ # Links without text are handled as [[link]] and not as [[link|text]], therefore reverse order of replacements
4353+ for pattern, replace in zip(reversed(SYNTAX_WIKI_PANGO), reversed(SYNTAX_WIKI_PANGO2)):
4354+ text = pattern[2].sub(replace[1], text)
4355+ return text
4356+
4357+ @staticmethod
4358+ def zim_to_cell(text):
4359+ for pattern, replace in zip(SYNTAX_WIKI_PANGO, SYNTAX_WIKI_PANGO2):
4360+ text = pattern[0].sub(replace[1], text)
4361+ return text
4362+
4363+ @staticmethod
4364+ def cell_to_zim(text):
4365+ for pattern, replace in zip(SYNTAX_WIKI_PANGO, SYNTAX_WIKI_PANGO2):
4366+ text = pattern[1].sub(replace[0], text)
4367+ return text
4368+
4369+@extends('MainWindow')
4370+class MainWindowExtension(WindowExtension):
4371+ '''
4372+ Connector between the zim application with its toolbar and menu and the tableview-object
4373+ In GTK there is no native table symbol. So this image is needed: data/pixmaps/insert-table.png
4374+ '''
4375+ uimanager_xml = '''
4376+ <ui>
4377+ <menubar name='menubar'>
4378+ <menu action='insert_menu'>
4379+ <placeholder name='plugin_items'>
4380+ <menuitem action='insert_table'/>
4381+ </placeholder>
4382+ </menu>
4383+ </menubar>
4384+ <toolbar name='toolbar'>
4385+ <placeholder name='format'>
4386+ <toolitem action='insert_table'/>
4387+ </placeholder>
4388+ </toolbar>
4389+ </ui>
4390+ '''
4391+
4392+ ''' static reference to window object, necessary for access to pageview '''
4393+ window = None
4394+
4395+ def __init__(self, plugin, window):
4396+ ''' Constructor '''
4397+ WindowExtension.__init__(self, plugin, window)
4398+ MainWindowExtension.window = window
4399+
4400+ ObjectManager.register_object(OBJECT_TYPE, self.plugin.create_table, self)
4401+
4402+ # reload tables on current page after plugin activation
4403+ if self.window.ui.page:
4404+ self.window.ui.reload_page()
4405+
4406+ def teardown(self):
4407+ ''' Deconstructor '''
4408+ ObjectManager.unregister_object(OBJECT_TYPE)
4409+ self.window.ui.reload_page()
4410+
4411+ @staticmethod
4412+ def get_geometry():
4413+ '''Returns tuple of geometry data from wiki textview window. Useful for setting width of object'''
4414+ win = MainWindowExtension.window.pageview.view.get_window(gtk.TEXT_WINDOW_TEXT)
4415+ geometry = win.get_geometry() if hasattr(win, 'get_geometry') else None
4416+ return geometry
4417+
4418+ @action(_('Table'), stock='zim-insert-table', readonly=False) # T: menu item
4419+ def insert_table(self):
4420+ '''Run the InsertTableDialog'''
4421+ col_model = EditTableDialog(self.window, self.plugin, self.window.pageview).run()
4422+ if not col_model:
4423+ return
4424+
4425+ _ids, headers, aligns, wraps = ([], [], [], [])
4426+
4427+ for model in col_model:
4428+ headers.append(model[1])
4429+ aligns.append(model[3])
4430+ wraps.append(model[2])
4431+
4432+ attrs = {'aligns': aligns, 'wraps': wraps}
4433+
4434+ obj = self.plugin.create_table(attrs, [headers])
4435+ obj.attrib = {'type': OBJECT_TYPE}
4436+ pageview = self.window.pageview
4437+ pageview.insert_table_at_cursor(obj)
4438+
4439+ def do_edit_object(self, obj):
4440+ '''
4441+ With the right button press a context-menu is opened and a table can then be edited
4442+ '''
4443+ self.do_edit_table(obj)
4444+
4445+ def do_edit_table(self, obj):
4446+ '''Run the EditTableDialog '''
4447+
4448+ aligns = obj.get_aligns()
4449+ wraps = obj.get_wraps()
4450+ titles = [col.get_title() for col in obj.treeview.get_columns()]
4451+ old_model = []
4452+ for i in range(len(titles)):
4453+ old_model.append([i, titles[i], aligns[i], wraps[i]])
4454+ new_model = EditTableDialog(self.window, self.plugin, self.window.pageview, old_model).run()
4455+
4456+ if new_model:
4457+ self._update_table_view(obj, new_model)
4458+
4459+ def _update_table_view(self, obj, new_model):
4460+ '''
4461+ Replaces liststore of currently displayed treeview with updated data and fixes references to attributes
4462+ :param obj: tableview object
4463+ :param new_model: tuple of lists for ([id], [header], [warps], [aligns])
4464+ '''
4465+ # prepare results out of dialog-window
4466+ id_mapping, headers, aligns, wraps = ({}, [], [], [])
4467+ for i, model in enumerate(new_model):
4468+ if model[0] != -1:
4469+ id_mapping[i] = model[0]
4470+ header = model[1] if model[1] else ' '
4471+ headers.append(header)
4472+ aligns.append(model[3])
4473+ wraps.append(model[2])
4474+
4475+ # creation of new table-view widget
4476+ attrs = {'aligns': aligns, 'wraps': wraps}
4477+ newrows = self._calculate_new_liststore(obj.treeview.get_model(), id_mapping, len(headers))
4478+ widget = TableViewWidget(obj, headers, newrows, attrs)
4479+ new_treeview = widget.get_treeview()
4480+ new_model = new_treeview.get_model()
4481+
4482+ # update of displayed table(=treeview) and its structure (=liststore)
4483+ obj.treeview.set_model(new_model)
4484+ # remove all old columns and move new columns to original treeview
4485+ for col in obj.treeview.get_columns():
4486+ obj.treeview.remove_column(col)
4487+ for i, col in enumerate(new_treeview.get_columns()):
4488+ new_treeview.remove_column(col)
4489+ obj.treeview.append_column(col)
4490+ title_label = TableViewWidget.create_headerlabel(headers[i])
4491+ col.set_widget(title_label)
4492+
4493+ geometry = MainWindowExtension.get_geometry()[2] if MainWindowExtension.get_geometry() else 600
4494+ TableViewWidget.wrap_columns(obj.treeview, geometry, wraps)
4495+
4496+ obj.set_aligns(aligns)
4497+ obj.set_wraps(wraps)
4498+ obj.set_modified(True)
4499+
4500+ def _calculate_new_liststore(self, liststore, id_mapping, nr_cols):
4501+ ''' Old value of cells are used in the new table, but only if its column is not deleted '''
4502+ new_rows = []
4503+ for oldrow in liststore:
4504+ newrow = [' ']*nr_cols
4505+ for v, k in id_mapping.iteritems():
4506+ newrow[v] = oldrow[k]
4507+ new_rows.append(newrow)
4508+ return new_rows
4509+
4510+
4511+class TableViewObject(CustomObjectClass):
4512+ '''data presenter of an inserted table within a page'''
4513+ OBJECT_ATTR = {
4514+ 'type': String('table'),
4515+ 'aligns': String(''), # i.e. String(left,right,center)
4516+ 'wraps': String('') # i.e. String(0,1,0)
4517+ }
4518+
4519+ def __init__(self, attrib, header, rows, preferences):
4520+ '''
4521+ Creates a new object which can displayed within the page
4522+ :param attrib: aligns, wraps
4523+ :param header: titles of the table as list
4524+ :param rows: body-rows of the table as list of lists
4525+ :param preferences: optionally some preferences
4526+ '''
4527+ _attrib = {}
4528+ for k, v in attrib.iteritems():
4529+ if isinstance(v, list):
4530+ v = ','.join(map(str, v))
4531+ _attrib[k] = v
4532+ CustomObjectClass.__init__(self, _attrib, [header]+rows)
4533+
4534+ self._tableattrib = attrib
4535+ self._header = header
4536+ self._rows = rows
4537+ self.modified = False
4538+ self.preferences = preferences
4539+ self.treeview = None
4540+ self._widgets = WeakSet()
4541+ self.textview_geometry = None
4542+
4543+ # getters and setters for attributes
4544+ def get_aligns(self):
4545+ ''' get the list of align-attributes '''
4546+ return self._attrib['aligns'].split(',')
4547+
4548+ def set_aligns(self, data):
4549+ ''' Set list of align attributes for the current table. Each item belongs to a column.'''
4550+ assert(isinstance(data, list))
4551+ self._attrib['aligns'] = ','.join(data)
4552+
4553+ def get_wraps(self):
4554+ ''' get the list of wrap-attributes '''
4555+ return map(int, self._attrib['wraps'].split(','))
4556+
4557+ def set_wraps(self, data):
4558+ ''' Set list of wrap attributes for the current table. Each item belongs to a column.'''
4559+ assert(isinstance(data, list))
4560+ self._attrib['wraps'] = ','.join(str(item) for item in data)
4561+
4562+ def get_widget(self):
4563+ ''' Creates a new table-widget which can displayed on the wiki-page '''
4564+ attrib = {'aligns': self.get_aligns(), 'wraps': self.get_wraps()}
4565+ widget = TableViewWidget(self, self._header, self._rows, attrib)
4566+ treeview = widget.get_treeview()
4567+ self.treeview = treeview
4568+ liststore = treeview.get_model()
4569+ liststore.connect('row-changed', self.on_modified_changed)
4570+
4571+ self._widgets.add(widget)
4572+ widget.set_preferences(self.preferences)
4573+ return widget
4574+
4575+ def preferences_changed(self):
4576+ ''' Updates all created table-widgets, if preferences have changed '''
4577+ for widget in self._widgets:
4578+ widget.set_preferences(self.preferences)
4579+
4580+ def on_sort_column_changed(self, liststore):
4581+ ''' Trigger after a column-header is clicked and therefore its sort order has changed '''
4582+ self.set_modified(True)
4583+
4584+ def on_modified_changed(self, liststore, path, treeiter):
4585+ ''' Trigger after a table cell content is changed by the user '''
4586+ self.set_modified(True)
4587+
4588+ def get_data(self):
4589+ '''Returns table-object into textual data, for saving it as text.'''
4590+ liststore = self.treeview.get_model()
4591+ headers = []
4592+ rows = []
4593+
4594+ # parsing table header and attributes
4595+ for column in self.treeview.get_columns():
4596+ title = column.get_title() if column.get_title() else ' '
4597+ headers.append(title)
4598+ attrs = {'aligns': self._attrib['aligns'], 'wraps': self._attrib['wraps']}
4599+
4600+ # parsing rows
4601+ treeiter = liststore.get_iter_first()
4602+ while treeiter is not None:
4603+ row = []
4604+ for colid in range(len(self.treeview.get_columns())):
4605+ val = liststore.get_value(treeiter, colid) if liststore.get_value(treeiter, colid) else ' '
4606+ row.append(val)
4607+ rows.append(row)
4608+ treeiter = liststore.iter_next(treeiter)
4609+ rows = [map(lambda cell: CellFormatReplacer.cell_to_input(cell, True), row) for row in rows]
4610+
4611+ # logger.debug("Table as get-data: : %s, %s, %s", headers, rows, attrs)
4612+ return headers, rows, attrs
4613+
4614+
4615+ def dump(self, format, dumper, linker=None):
4616+ ''' Dumps currently structure for table into textual format - mostly used for debugging / testing purposes '''
4617+ return CustomObjectClass.dump(self, format, dumper, linker)
4618+
4619+
4620+class TableViewWidget(CustomObjectWidget):
4621+
4622+ def __init__(self, obj, headers, rows, attrs):
4623+ '''
4624+ This is a group of GTK Gui elements which are directly displayed within the wiki textarea
4625+ On initilizing also some signals are registered and a toolbar is initialized
4626+ :param obj: a Table-View-Object
4627+ :param headers: list of titles
4628+ :param rows: list of list of cells
4629+ :param attrs: table settings, like alignment and wrapping
4630+ :return:
4631+ '''
4632+ self.textarea_width = 0
4633+
4634+ # used in pageview
4635+ self._resize = True # attribute, that triggers resizing
4636+ self._has_cursor = False # Skip table object, if someone moves cursor around in textview
4637+
4638+ # used here
4639+ self.obj = obj
4640+ self._timer = None # NONE or number of current gobject.timer, which is running
4641+ self._keep_toolbar_open = False # a cell is currently edited, toolbar should not be hidden
4642+ self._cellinput_canceled = None # cell changes should be skipped
4643+ self._toolbar_enabled = True # sets if toolbar should be shown beneath a selected table
4644+
4645+ gtk.EventBox.__init__(self)
4646+ self.set_border_width(5)
4647+
4648+ # Add vbox and wrap it to have a shadow around it
4649+ self.vbox = gtk.VBox() #: C{gtk.VBox} to contain widget contents
4650+
4651+ # Toolbar for table actions
4652+ toolbar = self.create_toolbar()
4653+ self.obj.toolbar = toolbar
4654+
4655+ # Actual gtk table object
4656+ self.treeview = self.create_treeview(headers, rows, attrs)
4657+
4658+ # Hook up signals & set options
4659+ self.treeview.connect('button-press-event', self.on_button_press_event)
4660+ self.treeview.connect('focus-in-event', self.on_focus_in, toolbar)
4661+ self.treeview.connect('focus-out-event', self.on_focus_out, toolbar)
4662+ self.treeview.connect('move-cursor', self.on_move_cursor)
4663+
4664+ # Set options
4665+ self.treeview.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_BOTH)
4666+ self.treeview.set_receives_default(True)
4667+ self.treeview.set_size_request(-1, -1)
4668+ self.treeview.set_border_width(2)
4669+
4670+ # disable interactive column search
4671+ self.treeview.set_enable_search(False)
4672+ gtk.binding_entry_remove(gtk.TreeView, gtk.keysyms.f, gtk.gdk.CONTROL_MASK)
4673+ self.treeview.set_search_column(-1)
4674+
4675+ # package gui elements
4676+ self.vbox.pack_end(toolbar)
4677+ self.add(self.vbox)
4678+ win = ScrolledWindow(self.treeview, gtk.POLICY_NEVER, gtk.POLICY_NEVER, gtk.SHADOW_OUT)
4679+ self.vbox.pack_start(win)
4680+
4681+ def on_focus_in(self, treeview, event, toolbar):
4682+ '''After a table is selected, this function will be triggered'''
4683+
4684+ self._keep_toolbar_open = False
4685+ if self._timer:
4686+ gobject.source_remove(self._timer)
4687+ if self._toolbar_enabled:
4688+ toolbar.show()
4689+
4690+ def on_focus_out(self, treeview, event, toolbar):
4691+ '''After a table is deselected, this function will be triggered'''
4692+ def receive_alarm():
4693+ if self._keep_toolbar_open:
4694+ self._timer = None
4695+ if self._timer:
4696+ self._timer = None
4697+ treeview.get_selection().unselect_all()
4698+ if self._toolbar_enabled:
4699+ toolbar.hide()
4700+ return False
4701+
4702+ self._timer = gobject.timeout_add(500, receive_alarm)
4703+
4704+ def create_toolbar(self):
4705+ '''This function creates a toolbar which is displayed next to the table'''
4706+ toolbar = gtk.Toolbar()
4707+ toolbar.set_orientation(gtk.ORIENTATION_HORIZONTAL)
4708+ toolbar.set_style(gtk.TOOLBAR_ICONS)
4709+ toolbar.set_border_width(1)
4710+
4711+ tooltips = gtk.Tooltips()
4712+ for pos, stock, handler, data, tooltip in (
4713+ (0, gtk.STOCK_ADD, self.on_add_row, None, _('Add row')), # T: tooltip on mouse hover
4714+ (1, gtk.STOCK_DELETE, self.on_delete_row, None, _('Remove row')), # T: tooltip on mouse hover
4715+ (2, gtk.STOCK_COPY, self.on_clone_row, None, _('Clone row')), # T: tooltip on mouse hover
4716+ (3, None, None, None, None),
4717+ (4, gtk.STOCK_GO_UP, self.on_move_row, -1, _('Row up')), # T: tooltip on mouse hover
4718+ (5, gtk.STOCK_GO_DOWN, self.on_move_row, 1, _('Row down')), # T: tooltip on mouse hover
4719+ (6, None, None, None, None),
4720+ (7, gtk.STOCK_PREFERENCES, self.on_change_columns, None, _('Change columns')), # T: tooltip on mouse hover
4721+ (8, None, None, None, None),
4722+ (9, gtk.STOCK_HELP, self.on_open_help, None, _('Open help')), # T: tooltip on mouse hover
4723+ ):
4724+ if stock is None:
4725+ toolbar.insert(gtk.SeparatorToolItem(), pos)
4726+ else:
4727+ button = gtk.ToolButton(stock)
4728+ if data:
4729+ button.connect('clicked', handler, data)
4730+ else:
4731+ button.connect('clicked', handler)
4732+ tooltips.set_tip(button, tooltip)
4733+ toolbar.insert(button, pos)
4734+
4735+ toolbar.set_size_request(300,-1)
4736+ toolbar.set_icon_size(gtk.ICON_SIZE_MENU)
4737+
4738+ return toolbar
4739+
4740+ def toolbar_hide(self):
4741+ ''' Hide toolbar of the table (moving rows around, etc.) '''
4742+ self.obj.toolbar.hide()
4743+
4744+ def resize_to_textview(self, view):
4745+ ''' Overriding - on resizing the table should not expanded to 100% width'''
4746+ win = view.get_window(gtk.TEXT_WINDOW_TEXT)
4747+ if not win or win.get_geometry()[2] == self.textarea_width:
4748+ return
4749+
4750+ self.textarea_width = win.get_geometry()[2]
4751+ self.wrap_columns(self.treeview, self.textarea_width, self.obj.get_wraps())
4752+
4753+ def wrap_columns(self, treeview, textarea_width, wraps):
4754+ TableViewWidget.wrap_columns(treeview, textarea_width, wraps)
4755+
4756+ @staticmethod
4757+ def wrap_columns(treeview, textarea_width, wraps):
4758+ ''' Wrap all columns, which should be wrapped '''
4759+ nrcols = len(treeview.get_columns())
4760+
4761+ if wraps == [1] * nrcols: # in case all columns are wrapped
4762+ max_width = textarea_width - 38 - nrcols * 10 if textarea_width else -1
4763+ else:
4764+ max_width = textarea_width - 40
4765+ if (not gtk.gtk_version >= (2, 8)) or max_width <= 0 or not textarea_width:
4766+ return
4767+
4768+ for column, wrap in zip(treeview.get_columns(), wraps):
4769+ cell = column.get_cell_renderers()[0]
4770+ cell.set_property('yalign', 0.0) # no vertical alignment, text starts on the top
4771+ if wrap == 1:
4772+ cell.set_property('wrap-width', max_width//nrcols)
4773+ cell.set_property('wrap-mode', pango.WRAP_WORD)
4774+
4775+ column.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE) # allow column shrinks
4776+ column.set_max_width(0) # shrink column
4777+ column.set_max_width(-1) # reset value
4778+ column.set_sizing(gtk.TREE_VIEW_COLUMN_GROW_ONLY) # reset value
4779+
4780+ def _column_alignment(self, aligntext):
4781+ ''' The column alignment must be converted from numeric to keywords '''
4782+ if aligntext == 'left':
4783+ align = 0.0
4784+ elif aligntext == 'center':
4785+ align = 0.5
4786+ elif aligntext == 'right':
4787+ align = 1.0
4788+ else:
4789+ align = None
4790+ return align
4791+
4792+ def create_treeview(self, headers, rows, attrs):
4793+ '''
4794+ Initializes a treeview with its model (liststore) and all its columns
4795+ :param headers: a list of title values for the column-headers
4796+ :param rows: a list of list of cells, for the table body
4797+ :param attrs: some more attributes, which define the layout of a column
4798+ :return: gtk.treeview
4799+ '''
4800+ nrcols = len(headers)
4801+
4802+ cols = [str]*nrcols
4803+
4804+ liststore = gtk.ListStore(*cols)
4805+ treeview = gtk.TreeView(liststore)
4806+ for trow in rows:
4807+ liststore.append(trow)
4808+
4809+ for i, headcol in enumerate(headers):
4810+ cell = gtk.CellRendererText()
4811+ tview_column = gtk.TreeViewColumn(headcol, cell)
4812+ treeview.append_column(tview_column)
4813+
4814+ # set title as label
4815+ header_label = self.create_headerlabel(headcol)
4816+ tview_column.set_widget(header_label)
4817+
4818+ # set properties of column
4819+ tview_column.set_attributes(cell, markup=i)
4820+ cell.set_property('editable', True)
4821+ tview_column.set_sort_column_id(i)
4822+ # set sort function
4823+ liststore.set_sort_func(i, self.sort_by_number_or_string, i)
4824+ # set alignment - left center right
4825+ align = self._column_alignment(attrs['aligns'][i])
4826+ if align:
4827+ tview_column.set_alignment(align)
4828+ cell.set_alignment(align, 0.0)
4829+
4830+ # callbacks after an action
4831+ cell.connect('edited', self.on_cell_changed, treeview.get_model(), i)
4832+ cell.connect('editing-started', self.on_cell_editing_started, treeview.get_model(), i)
4833+ cell.connect('editing-canceled', self.on_cell_editing_canceled)
4834+
4835+ return treeview
4836+
4837+ def create_headerlabel(self, title):
4838+ return TableViewWidget.create_headerlabel(title)
4839+
4840+ @staticmethod
4841+ def create_headerlabel(title):
4842+ ''' Sets options for the treeview header'''
4843+ col_widget = gtk.VBox()
4844+ col_widget.show()
4845+
4846+
4847+ col_label = gtk.Label('<u>'+title+'</u>')
4848+ col_label.set_use_markup(True)
4849+ col_label.show()
4850+ col_widget.pack_start(col_label)
4851+ #col_align.add(col_label)
4852+
4853+ '''col_entry = InputEntry()
4854+ col_entry.set_name('treeview-header-entry')
4855+ col_entry.show()
4856+ col_widget.pack_start(col_entry)'''
4857+
4858+ return col_widget
4859+
4860+ def get_treeview(self):
4861+ # treeview of current table
4862+ return self.treeview
4863+
4864+ def set_preferences(self, preferences):
4865+ self._toolbar_enabled = preferences['show_helper_toolbar']
4866+
4867+ ''' Sets general plugin settings for this object'''
4868+ grid_option = self.pref_gridlines(preferences['grid_lines'])
4869+ self.treeview.set_grid_lines(grid_option)
4870+ pass
4871+
4872+ def pref_gridlines(self, option):
4873+ if option == LINES_BOTH:
4874+ return gtk.TREE_VIEW_GRID_LINES_BOTH
4875+ elif option == LINES_NONE:
4876+ return gtk.TREE_VIEW_GRID_LINES_NONE
4877+ elif option == LINES_HORIZONTAL:
4878+ return gtk.TREE_VIEW_GRID_LINES_HORIZONTAL
4879+ elif option == LINES_VERTICAL:
4880+ return gtk.TREE_VIEW_GRID_LINES_VERTICAL
4881+
4882+ def on_move_cursor(self, view, step_size, count):
4883+ ''' If you try to move the cursor out of the tableditor release the cursor to the parent textview '''
4884+ return None # let parent handle this signal
4885+
4886+ def fetch_cell_by_event(self, event, treeview):
4887+ ''' Looks for the cell where the mouse clicked on it '''
4888+ liststore = treeview.get_model()
4889+ (xpos, ypos) = event.get_coords()
4890+ (treepath, treecol, xrel, yrel) = treeview.get_path_at_pos(int(xpos), int(ypos))
4891+ treeiter = liststore.get_iter(treepath)
4892+ cellvalue = liststore.get_value(treeiter, treeview.get_columns().index(treecol))
4893+ return cellvalue
4894+
4895+ def get_linkurl(self, celltext):
4896+ ''' Checks a cellvalue if it contains a link and returns only the link value '''
4897+ linkregex = r'<span foreground="blue">.*?<span.*?>(.*?)</span></span>'
4898+ matches = re.match(linkregex, celltext)
4899+ linkvalue = matches.group(1) if matches else None
4900+ return linkvalue
4901+
4902+ def on_button_press_event(self, treeview, event):
4903+ '''
4904+ Displays a context-menu on right button click
4905+ Opens the link of a tablecell on CTRL pressed and left button click
4906+ '''
4907+ if event.type == gtk.gdk.BUTTON_PRESS and event.button == 1 and event.get_state() & gtk.gdk.CONTROL_MASK:
4908+ # With CTRL + LEFT-Mouse-Click link of cell is opened
4909+ cellvalue = self.fetch_cell_by_event(event, treeview)
4910+ linkvalue = self.get_linkurl(cellvalue)
4911+ if linkvalue:
4912+ self.obj.emit('link-clicked', {'href': linkvalue})
4913+ return
4914+
4915+ if event.type == gtk.gdk.BUTTON_PRESS and event.button == 3:
4916+ # Right button opens context menu
4917+ self._keep_toolbar_open = True
4918+ cellvalue = self.fetch_cell_by_event(event, treeview)
4919+ linkvalue = self.get_linkurl(cellvalue)
4920+ linkitem_is_activated = (linkvalue is not None)
4921+
4922+ menu = gtk.Menu()
4923+
4924+ for stock, handler, data, tooltip in (
4925+ (gtk.STOCK_ADD, self.on_add_row, None, _('Add row')), # T: menu item
4926+ (gtk.STOCK_DELETE, self.on_delete_row, None, _('Delete row')), # T: menu item
4927+ (gtk.STOCK_COPY, self.on_clone_row, None, _('Clone row')), # T: menu item
4928+ (None, None, None, None), # T: menu item
4929+ (gtk.STOCK_JUMP_TO, self.on_open_link, linkvalue, _('Open cell content link')), # T: menu item
4930+ (None, None, None, None),
4931+ (gtk.STOCK_GO_UP, self.on_move_row, -1, _('Row up')), # T: menu item
4932+ (gtk.STOCK_GO_DOWN, self.on_move_row, 1, _('Row down')), # T: menu item
4933+ (None, None, None, None),
4934+ (gtk.STOCK_PREFERENCES, self.on_change_columns, None, _('Change columns')) # T: menu item
4935+ ):
4936+
4937+ if stock is None:
4938+ menu.append(gtk.SeparatorMenuItem())
4939+ else:
4940+ item = gtk.ImageMenuItem(stock)
4941+ item.set_always_show_image(True)
4942+ item.set_label(_(tooltip))
4943+ if data:
4944+ item.connect_after('activate', handler, data)
4945+ else:
4946+ item.connect_after('activate', handler)
4947+ if handler == self.on_open_link:
4948+ item.set_sensitive(linkitem_is_activated)
4949+ menu.append(item)
4950+
4951+ menu.show_all()
4952+ menu.popup(None, None, None, event.button, event.time)
4953+
4954+ def on_add_row(self, action):
4955+ ''' Context menu: Add a row '''
4956+ selection = self.treeview.get_selection()
4957+ model, treeiter = selection.get_selected()
4958+ if not treeiter: # no selected item
4959+ self.selection_info()
4960+ return
4961+
4962+ row = len(self.treeview.get_columns())*['']
4963+ path = model.insert_after(treeiter, row)
4964+ self.obj.set_modified(True)
4965+
4966+ def on_clone_row(self, action):
4967+ ''' Context menu: Clone a row '''
4968+ selection = self.treeview.get_selection()
4969+ model, treeiter = selection.get_selected()
4970+ if not treeiter: # no selected item
4971+ self.selection_info()
4972+ return
4973+
4974+ path = model.get_path(treeiter)
4975+ row = model[path[0]]
4976+ model.insert_after(treeiter, row)
4977+ self.obj.set_modified(True)
4978+
4979+ def on_delete_row(self, action):
4980+ ''' Context menu: Delete a row '''
4981+ selection = self.treeview.get_selection()
4982+ model, treeiter = selection.get_selected()
4983+ if not treeiter: # no selected item
4984+ self.selection_info()
4985+ return
4986+
4987+ if len(model) > 1:
4988+ model.remove(treeiter)
4989+ self.obj.set_modified(True)
4990+ else:
4991+ md = gtk.MessageDialog(None, gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_WARNING, gtk.BUTTONS_CLOSE,
4992+ _("The table must consist of at least on row!\n No deletion done."))
4993+ # T: Popup dialog
4994+ md.run()
4995+ md.destroy()
4996+
4997+ def on_move_row(self, action, direction):
4998+ ''' Trigger for moving a row one position up/down '''
4999+ selection = self.treeview.get_selection()
5000+ model, treeiter = selection.get_selected()
The diff has been truncated for viewing.