18d007c434087f7d5b4ba2a9f68a649d58911a01
[Misc/ipe.git] / ipelets / presentation / presentation.lua
1 ----------------------------------------------------------------------
2 -- presentation goodies for Ipe
3
4 --[[
5
6 SUMMARY
7
8  This ipelet adds a few goodies for presentation, including
9   1. Ability to create beamer-like boxes with/without header
10   2. Add a framed box for the selected objects (including text)
11   3. A function to deselect all selected on all pages
12   4. The boxes can be edited as text/path objects (press E)
13   5. A few items can be added to the style sheet to add preferred
14      symbolics for box colors etc. This has the benefit that this
15      preferences can be changed to affect all boxes (see below).
16
17   The design of these boxes is from Martin Nöllenburg's presentation
18   example posted on the Ipe7 wiki page.
19
20 STYLESHEET (CHANGING PREFERRED SETTINGS)
21
22  Example style sheet to cascade with presentation.isy is as follows.
23
24 ---- content of example.isy ---
25 <?xml version="1.0"?>
26 <!DOCTYPE ipestyle SYSTEM "ipe.dtd">
27 <ipestyle name="mine">
28 <color name="tab_header" value="0 0 0.5"/>
29 <color name="tab_body" value="0.827"/>
30 <color name="box_fill" value="0.827"/>
31 <color name="box_border" value="0 0 0"/>
32 <pen name="boxborder" value="2.4"/>
33 <preamble>
34 \usepackage{amsmath}
35 </preamble>
36 </ipestyle>
37 ---- end content of example.isy ---
38
39  where:
40   tab_header= color of the tab header in a tabbed box
41   tab_body  = color of the tab body in a tabbed box
42   box_fill  = fill color a box
43   box_border= color of the box border
44   boxborder = linewidth of the box border
45
46  The preferred box mode (stroked/filled/strokedfilled) can be changed by
47  changing the hard-wired value (no stylesheet option) PREFERRED_BOX_MODE below
48
49  With the above style sheet, one can start an empty presentation using
50   ipe -sheet presentation -sheet /path/to/example.isy
51
52 SHORTCUT
53
54  Shortcuts to these functions can be changed as for other ipelets:
55         shortcuts.ipelet_x_presentation = "Key"
56  where x is the index (starting 1) of the sub-menu for the function
57
58 FILE/AUTHOR HISTORY
59
60  version  0. Initial Release. Zhengdao Wang 2010
61  version  1. add a line here if a change is made
62
63 LICENSE
64
65  This file can be distributed and modified under the terms of the GNU General
66  Public License as published by the Free Software Foundation; either version
67  3, or (at your option) any later version.
68
69  This file is distributed in the hope that it will be useful, but WITHOUT ANY
70  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
71  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
72  details.
73
74 --]]
75
76 ----------------------------------------------------------------------
77
78 label = "Presentation"
79
80 about = [[
81         Presentation Goodies: Add boxes around objects, deselect all,
82         add tabbed/boxed text.
83 ]]
84
85 V = ipe.Vector
86 indexOf=_G.indexOf
87
88 -- table storing the height of the first line box
89 Height={}
90 Spacing={}
91 Has_Height=nil
92
93 local c=10 -- corner size
94
95 local UNKNOWN=0
96 local TABBED_TEXT=1
97 local BOXED_TEXT=2
98 local BOXED_OTHER=3
99
100 local PREFERRED_BOX_MODE="strokedfilled" -- or stroked or filled
101
102 local BOX_DIALOG_SIZE={400,140}
103
104 -- "spline" is recommended here, as it should work with most
105 -- ipe-versions
106 local SPLINE_TYPE_NAME="spline"
107 -- local SPLINE_TYPE_NAME="oldspline"
108 -- local SPLINE_TYPE_NAME="bezier"
109
110 -- initialize the Height table
111 local function init_spacing(model)
112         local p=model:page()
113         local xx=300
114         local iH={}
115         local sizes = model.doc:sheets():allNames("textsize")
116         for i,size in ipairs(sizes) do
117                 obj=ipe.Text({textsize=size, minipage=true}, "ABC\n\nDEF", V(xx,xx), xx)
118                 local layer=p:active(model.vno)
119                 p:insert(nil, obj, nil, layer)
120                 obj=ipe.Text({textsize=size, minipage=true}, "ABC", V(xx,xx), xx)
121                 p:insert(nil, obj, nil, layer)
122                 iH[#iH+1]=size
123         end
124         model.doc:runLatex()
125         for i=#sizes,1,-1 do
126                 Height[iH[i]]=xx-p:bbox(#p):bottomLeft().y
127                 p:remove(#p)
128                 Spacing[iH[i]]=(xx-p:bbox(#p):bottomLeft().y)/2-Height[iH[i]]
129                 p:remove(#p)
130         end
131         Has_Height=1
132 end
133
134 -- generate a path with given properties
135 local function path(model, shape, props)
136         local oldvalues={}
137         for k,v in pairs(props) do
138                 oldvalues[k]=model.attributes[k]
139                 model.attributes[k]=v
140         end
141         local obj = ipe.Path(model.attributes, shape)
142         -- print(obj)
143         for k,v in pairs(props) do
144                 model.attributes[k]=oldvalues[k]
145         end
146         if props.pen then obj:set('pen', props.pen) end
147         obj:set('pathmode', props.pathmode, props.stroke, props.fill)
148         return obj
149 end
150
151 -- create a square box #shape=3
152 local function boxshape_square(v1, v2)
153         return { type="curve", closed=true;
154                  { type="segment"; v1, V(v1.x, v2.y) }, -- L
155                  { type="segment"; V(v1.x, v2.y), v2 }, -- T
156                  { type="segment"; v2, V(v2.x, v1.y) } } -- R
157 end
158
159 -- create a square box with a pointer #shape=6
160 local function boxshape_square_pointer(v1, v2, v3, v4, v5)
161         local dx=v2.x-v1.x
162         local dy=v2.y-v1.y
163         local v3=v3 or V(v1.x+.4*dx, v2.y)
164         local v4=v4 or V(v1.x+.6*dx,v2.y+.3*dy)
165         local v5=v5 or V(v1.x+.5*dx,v2.y)
166         return { type="curve", closed=true;
167                  { type="segment"; v1, V(v1.x, v2.y) }, -- L
168                  { type="segment"; V(v1.x, v2.y), v3}, -- T
169                  { type="segment"; v3, v4}, -- P
170                  { type="segment"; v4, v5}, --P
171                  { type="segment"; v5, v2}, -- T
172                  { type="segment"; v2, V(v2.x, v1.y) } } -- R
173 end
174
175 -- create a header box: round corner on topRight
176 local function boxshape_roundTR(v1, v2)
177         return { type="curve", closed=true;
178                  { type="segment"; v1, V(v1.x, v2.y) },
179                  { type="segment"; V(v1.x, v2.y), V(v2.x-c, v2.y) },
180                  { type=SPLINE_TYPE_NAME; V(v2.x-c, v2.y), V(v2.x-c/2, v2.y),
181                         V(v2.x, v2.y-c/2), V(v2.x, v2.y-c) },
182                  { type="segment"; V(v2.x, v2.y-c), V(v2.x, v1.y) } }
183 end
184
185 -- create a body box: round corner on bottom Left
186 local function boxshape_roundLL(v1, v2)
187         return { type="curve", closed=true;
188                  { type="segment"; v2, V(v2.x, v1.y) },
189                  { type="segment"; V(v2.x, v1.y), V(v1.x+c, v1.y) },
190                  { type=SPLINE_TYPE_NAME; V(v1.x+c, v1.y), V(v1.x+c/2,v1.y),
191                         V(v1.x, v1.y+c/2), V(v1.x, v1.y+c) },
192                  { type="segment"; V(v1.x, v1.y+c), V(v1.x, v2.y) } }
193 end
194
195 -- create a body box: 4 round corners #shape=8
196 local function boxshape_round(v1, v2)
197         return { type="curve", closed=true;
198                  { type="segment"; V(v2.x, v2.y-c), V(v2.x, v1.y+c) }, -- R
199                  { type=SPLINE_TYPE_NAME; V(v2.x,v1.y+c), V(v2.x, v1.y+c/2),
200                         V(v2.x-c/2,v1.y), V(v2.x-c,v1.y)}, -- BR
201                  { type="segment"; V(v2.x-c, v1.y), V(v1.x+c, v1.y) }, -- B
202                  { type=SPLINE_TYPE_NAME; V(v1.x+c, v1.y), V(v1.x+c/2,v1.y),
203                         V(v1.x, v1.y+c/2), V(v1.x, v1.y+c) }, -- BL
204                  { type="segment"; V(v1.x, v1.y+c), V(v1.x, v2.y-c) }, -- L
205                  { type=SPLINE_TYPE_NAME; V(v1.x, v2.y-c), V(v1.x,v2.y-c/2),
206                         V(v1.x+c/2, v2.y), V(v1.x+c, v2.y) }, -- TL
207                  { type="segment"; V(v1.x+c, v2.y), V(v2.x-c, v2.y) }, -- T
208                  { type=SPLINE_TYPE_NAME; V(v2.x-c, v2.y), V(v2.x-c/2,v2.y),
209                         V(v2.x, v2.y-c/2), V(v2.x, v2.y-c) }, -- TR
210                 }
211 end
212
213 -- create a body box: 4 round corners, with pointer #shape=11
214 local function boxshape_round_pointer(v1, v2, v3, v4, v5)
215         local dx=v2.x-v1.x
216         local dy=v2.y-v1.y
217         local v3=v3 or V(v1.x+.4*dx, v2.y)
218         local v4=v4 or V(v1.x+.6*dx,v2.y+.3*dy)
219         local v5=v5 or V(v1.x+.5*dx,v2.y)
220         return { type="curve", closed=true;
221                  { type="segment"; V(v2.x, v2.y-c), V(v2.x, v1.y+c) }, -- R
222                  { type=SPLINE_TYPE_NAME; V(v2.x,v1.y+c), V(v2.x, v1.y+c/2),
223                         V(v2.x-c/2,v1.y), V(v2.x-c,v1.y)}, -- BR
224                  { type="segment"; V(v2.x-c, v1.y), V(v1.x+c, v1.y) }, -- B
225                  { type=SPLINE_TYPE_NAME; V(v1.x+c, v1.y), V(v1.x+c/2,v1.y),
226                         V(v1.x, v1.y+c/2), V(v1.x, v1.y+c) }, -- BL
227                  { type="segment"; V(v1.x, v1.y+c), V(v1.x, v2.y-c) }, -- L
228                  { type=SPLINE_TYPE_NAME; V(v1.x, v2.y-c), V(v1.x,v2.y-c/2),
229                         V(v1.x+c/2, v2.y), V(v1.x+c, v2.y) }, -- TL
230                  { type="segment"; V(v1.x+c, v2.y), v3}, -- T
231                  { type="segment"; v3, v4}, -- P
232                  { type="segment"; v4, v5}, --P
233                  { type="segment"; v5, V(v2.x-c, v2.y)}, -- T
234                  { type=SPLINE_TYPE_NAME; V(v2.x-c, v2.y), V(v2.x-c/2,v2.y),
235                         V(v2.x, v2.y-c/2), V(v2.x, v2.y-c) }, -- TR
236                 }
237 end
238
239 -- parse the values from a group obj
240 local function parse_group_values(model,prim)
241         local fs = model.doc:sheets():find("layout").framesize
242         local p=model:page()
243         local bbox=p:bbox(prim)
244         local pos=V(bbox:bottomLeft().x, bbox:topRight().y)
245
246         local elements=p[prim]:elements()
247         if #elements==4 then
248                 local hb,bb,ht,bt=elements[1],elements[2],elements[3],elements[4]
249                 if hb:type()=="path" and bb:type()=="path" and
250                         ht:type()=="text" and bt:type()=="text" then
251                         local values={htext=ht:text(),
252                                 btext=bt:text(),
253                                 pinned=(p[prim]:get("pinned")=="horizontal"),
254                                 fwidth=string.format('%.2f',
255                                         (bbox:topRight().x-bbox:bottomLeft().x)/fs.x),
256                                 hcolor=hb:get("fill"),
257                                 bcolor=bb:get("fill"),
258                                 size=ht:get("textsize")}
259                         return TABBED_TEXT,values,pos
260                 end
261         else
262                 local bb,bt=elements[1],elements[2]
263                 if bb:type()=="path" and #bb:shape()==1 and
264                         bb:shape()[1].closed==true and
265                         (#bb:shape()[1]==3 or #bb:shape()[1]==8 or
266                         #bb:shape()[1]==6 or #bb:shape()[1]==11) then
267                         if bt:type()=="text" then
268                                 local values={btext=bt:text(),
269                                         pinned=(p[prim]:get("pinned")=="horizontal"),
270                                         size=bt:get("textsize"),
271                                         fwidth=string.format('%.2f',
272                                                 (bbox:topRight().x-bbox:bottomLeft().x)/fs.x),
273                                         bcolor=bb:get("fill")}
274                                 if #bb:shape()[1]==6 then
275                                         pos=V(pos.x,bb:shape()[1][1][2].y)
276                                 elseif #bb:shape()[1]==11 then
277                                         pos=V(pos.x,bb:shape()[1][10][2].y)
278                                 end
279                                 return BOXED_TEXT,values,pos
280                         else
281                                 return BOXED_OTHER
282                         end
283                 end
284         end
285         return UNKNOWN
286 end
287
288 function mainWindow(model)
289    if model.ui.win == nil then
290       return model.ui
291    else
292       return model.ui:win()
293    end
294 end
295
296 -- Edit the values for the frame
297 local function edit_tabbed_values(model,values)
298         local d = ipeui.Dialog(mainWindow(model), "Create tabbed text")
299         local colors = model.doc:sheets():allNames("color")
300         local sizes = model.doc:sheets():allNames("textsize")
301         d:add("hlabel", "label", { label="Enter Header" }, 1, 1, 1, 1)
302         d:add("hcolor", "combo", colors, 1, 4)
303         d:add("htext", "input", { syntax="latex" }, 2, 1, 1, 4)
304         d:add("blabel", "label", { label="Enter Body"}, 3, 1, 1, 3)
305         d:add("bcolor", "combo", colors, 3, 4)
306         d:add("btext", "text", { syntax="latex" }, 4, 1, 1, 4)
307         d:add("size", "combo", sizes, 5, 1)
308         d:add("wlabel", "label", { label="width [0-1]"}, 5, 2, 1, 2)
309         d:add("fwidth", "input", {size=2}, 5, 3, 1, 1)
310         d:add("pinned", "checkbox", { label="pinned"}, 5, 4)
311         d:add("ok", "button", { label="&Ok", action="accept" }, 6, 4)
312         d:add("cancel", "button", { label="&Cancel", action="reject" }, 6, 3)
313         _G.addEditorField(d, "btext", 6, 2)
314         d:setStretch("row", 2, 1)
315         d:setStretch("column", 1, 1)
316         d:set("fwidth", "0.8")
317         d:set("pinned", 1)
318
319         if indexOf("tab_header", colors) then
320                 d:set("hcolor", indexOf("tab_header", colors))
321         elseif indexOf("darkblue", colors) then
322                 d:set("hcolor", indexOf("darkblue", colors))
323         else
324                 d:set("hcolor", indexOf("black", colors))
325         end
326
327         if indexOf("tab_body", colors) then
328                 d:set("bcolor", indexOf("tab_body", colors))
329         elseif indexOf("lightgray", colors) then
330                 d:set("bcolor", indexOf("lightgray", colors))
331         else
332                 d:set("bcolor", indexOf("white", colors))
333         end
334
335         if values then
336                 for k,v in pairs(values) do
337                         if k=="hcolor" or k=="bcolor" then v=indexOf(v, colors) end
338                         if k=="size" then v=indexOf(v, sizes) end
339                         d:set(k, v)
340                 end
341         end
342
343         local r = d:execute(prefs.editor_size)
344         if not r then return end
345         local newvalues={}
346         newvalues.htext=d:get("htext")
347         newvalues.btext=d:get("btext")
348         newvalues.pinned=d:get("pinned")
349         newvalues.fwidth=d:get("fwidth")
350         newvalues.size=sizes[d:get("size")]
351         newvalues.hcolor=colors[d:get("hcolor")]
352         newvalues.bcolor=colors[d:get("bcolor")]
353 --      if newvalues.fwidth=="" or tonumber(newvalues.fwidth)>.99 then
354 --              newvalues.pinned=true
355 --      end
356         return newvalues
357 end
358
359 -- measure the height a piece of given text
360 local function measure_height(model,text,size,width)
361         local p=model:page()
362         local obj= ipe.Text(model.attributes, text, V(0,0), width)
363         obj:set('textsize', size)
364         local layer=p:active(model.vno)
365         p:insert(nil, obj, nil, layer)
366         if not model.doc:runLatex() then
367                 p:remove(#p)
368                 return 100
369         end
370         local bbox=p:bbox(#p)
371         p:remove(#p)
372         return bbox:topRight().y-bbox:bottomLeft().y
373 end
374
375 -- Create boxed text
376 local function create_boxed(model,values, pos, prim)
377         local fs = model.doc:sheets():find("layout").framesize
378         local p = model:page()
379         local editmode=(prim~=nil)
380
381         local width fwidth=tonumber(values.fwidth)
382
383         if not fwidth or fwidth<0 or fwidth>1 then
384                 width=fs.x
385                 fwidth=1
386         else
387                 width=fwidth*fs.x
388         end
389
390         -- spacing
391         local s=Spacing[values.size]
392         local h=Height[values.size]
393         if not s or not h then
394                 init_spacing(model)
395                 s=Spacing[values.size]
396                 h=Height[values.size]
397         end
398
399         local bheight=measure_height(model,values.btext,values.size,width-2*s)
400
401         -- location
402         if not pos then
403                 x1=fs.x/2-width/2
404                 x2=fs.x/2+width/2
405                 y2=fs.y/2
406                 y1=y2-bheight-1.8*s
407         else
408                 x1=pos.x
409                 x2=x1+width
410                 y2=pos.y
411                 y1=y2-bheight-1.8*s
412         end
413         if fwidth>.99 then x1,x2=0,fs.x end
414
415         -- body text
416         pos=V(x1+s, y2-s)
417         local bt= ipe.Text(model.attributes, values.btext, pos, width-2*s)
418         bt:set('textsize', values.size)
419
420         -- body box
421         local shape2
422         if values.rounded then
423                 shape2 = { boxshape_round(V(x1,y1), V(x2,y2)) }
424         else
425                 shape2 = { boxshape_square(V(x1,y1), V(x2,y2)) }
426         end
427         local bb = path(model, shape2,
428                 {pathmode='filled', fill=values.bcolor, stroke="white"})
429
430         -- group object
431         local elements={bb,bt}
432         local obj=ipe.Group(elements)
433         -- obj:setMatrix(p[prim]:matrix()) -- currently not working
434         if values.pinned then obj:set('pinned', 'horizontal') end
435
436         if editmode then
437                 local t={original=p[prim]:clone(),
438                                 label="edit boxed text",
439                                 pno=model.pno,
440                                 vno=model.vno,
441                                 primary=prim,
442                                 final=obj }
443                 t.undo = function (t, doc)
444                                          doc[t.pno]:replace(t.primary, t.original)
445                                  end
446                 t.redo = function (t, doc)
447                                          doc[t.pno]:replace(t.primary, t.final)
448                                  end
449                 model:register(t)
450         else
451                 model:creation("create boxed text", obj)
452                 -- model.doc:runLatex() -- may crash the thing
453         end
454 end
455
456 -- Create the requested object from values
457 local function create_tabbed(model,values, pos, prim)
458         local fs = model.doc:sheets():find("layout").framesize
459         local p = model:page()
460         local editmode=(prim~=nil)
461
462         local width fwidth=tonumber(values.fwidth)
463
464         if not fwidth or fwidth<0 or fwidth>1 then
465                 width=fs.x
466                 fwidth=1
467         else
468                 width=fwidth*fs.x
469         end
470
471         -- spacing
472         local s=Spacing[values.size]
473         local h=Height[values.size]
474         if not s or not h then
475                 init_spacing(model)
476                 s=Spacing[values.size]
477                 h=Height[values.size]
478         end
479
480         local bheight=measure_height(model,values.btext,values.size,width-2*s)
481
482         -- location
483         if not pos then
484                 x1=fs.x/2-width/2
485                 x2=fs.x/2+width/2
486                 y2=fs.y/2
487                 y1=y2-h-bheight-3.8*s
488         else
489                 x1=pos.x
490                 x2=x1+width
491                 y2=pos.y
492                 y1=y2-h-bheight-3.8*s
493         end
494         if fwidth>.99 then x1,x2=0,fs.x end
495
496         -- header text
497         pos=V(x1+s, y2-s)
498         local ht= ipe.Text(model.attributes, values.htext, pos, width-2*s)
499         ht:set('stroke', 'white')
500         ht:set('textsize', values.size)
501
502         -- body text
503         pos=V(x1+s, y2-s-h-2*s)
504         local bt= ipe.Text(model.attributes, values.btext, pos, width-2*s)
505         bt:set('textsize', values.size)
506
507         -- header box
508         local shape1 = { boxshape_roundTR(V(x1,y2-h-2*s), V(x2,y2)) }
509         local hb = path(model, shape1,
510                         {pathmode='filled', fill=values.hcolor, stroke="white"})
511         hb:set('pathmode', 'filled', "white", values.hcolor)
512
513         -- body box
514         local shape2 = { boxshape_roundLL(V(x1,y1), V(x2,y2-h-2*s)) }
515         local bb = path(model, shape2,
516                 {pathmode='filled', fill=values.bcolor, stroke="white"})
517
518         -- group object
519         local elements={hb,bb,ht,bt}
520         local obj=ipe.Group(elements)
521         if values.pinned then obj:set('pinned', 'horizontal') end
522
523         if editmode then
524                 local t={original=p[prim]:clone(),
525                                 label="edit tabbed text",
526                                 pno=model.pno,
527                                 vno=model.vno,
528                                 primary=prim,
529                                 final=obj }
530                 t.undo = function (t, doc)
531                                          doc[t.pno]:replace(t.primary, t.original)
532                                  end
533                 t.redo = function (t, doc)
534                                          doc[t.pno]:replace(t.primary, t.final)
535                                  end
536                 model:register(t)
537         else
538                 model:creation("create tabbed text", obj)
539                 -- model.doc:runLatex() -- may crash the thing
540         end
541
542 end
543
544 -- create the dialog for editing box properties
545 local function box_property_dialog(model)
546         local colors = model.doc:sheets():allNames("color")
547         local pens= model.doc:sheets():allNames("pen")
548         local pathmodes = {"stroked", "strokedfilled", "filled"}
549         local d = ipeui.Dialog(mainWindow(model), "Edit box properties")
550
551         d:add("rounded", "checkbox", { label="Round Corner"}, 1, 1, 1, 1)
552         d:add("pointer", "checkbox", { label="Pointer"}, 1, 2, 1, 1)
553         d:add("mlabel", "label", { label="Mode"}, 2, 1)
554         d:add("pathmode", "combo", pathmodes, 2, 2)
555         d:add("flabel", "label", { label="Fill Color" }, 3, 1)
556         d:add("fill", "combo", colors, 3, 2)
557         d:add("slabel", "label", { label="Stroke Color" }, 4, 1)
558         d:add("stroke", "combo", colors, 4, 2)
559         d:add("plabel", "label", { label="Line Width"}, 5, 1)
560         d:add("pen", "combo", pens, 5, 2)
561         d:add("cancel", "button", { label="&Cancel", action="reject" }, 6, 1)
562         d:add("ok", "button", { label="&Ok", action="accept" }, 6, 2)
563         d:setStretch("column", 2, 1)
564         return d
565 end
566
567 -- edit a box object
568 local function edit_box(model, prim)
569         local colors = model.doc:sheets():allNames("color")
570         local pens= model.doc:sheets():allNames("pen")
571         local pathmodes = {"stroked", "strokedfilled", "filled"}
572
573         local p=model:page()
574         local elements=p[prim]:elements()
575         local bb=elements[1]
576         local bbs=bb:shape()[1]
577
578         local d=box_property_dialog(model)
579
580         -- default values
581         d:set("rounded", #bbs>=7)
582         d:set("pathmode", indexOf(bb:get('pathmode'),pathmodes))
583         d:set("pointer", #bbs==6 or #bbs==11)
584         if indexOf(bb:get('stroke'),colors) then
585                 d:set("stroke", indexOf(bb:get('stroke'),colors) )
586         elseif not model.attributes.stroke then
587                 d:set("stroke", indexOf(model.attributes.stroke,colors) )
588         end
589         if indexOf(bb:get('fill'),colors) then
590                 d:set("fill", indexOf(bb:get('fill'),colors) )
591         elseif not model.attributes.fill then
592                 d:set("fill", indexOf(model.attributes.fill,colors) )
593         end
594         if indexOf(bb:get('pen'),pens) then
595                 d:set("pen", indexOf(bb:get('pen'),pens) )
596         elseif not model.attributes.pen then
597                 d:set("pen", indexOf(model.attributes.pen,pens) )
598         end
599
600         local r = d:execute(BOX_DIALOG_SIZE)
601         if not r then return end
602         local pathmode=pathmodes[d:get("pathmode")]
603         local stroke=colors[d:get("stroke")]
604         local fill=colors[d:get("fill")]
605         local pen=pens[d:get("pen")]
606
607         local boxshape
608         if d:get('rounded') and d:get('pointer') then
609                 boxshape=boxshape_round_pointer
610         elseif d:get('rounded') and not d:get('pointer') then
611                 boxshape=boxshape_round
612         elseif not d:get('rounded') and d:get('pointer') then
613                 boxshape=boxshape_square_pointer
614         else
615                 boxshape=boxshape_square
616         end
617
618         -- v1=BL, v2=TR, v3=P1, v4=P2, v5=P3. Pointer=(P1,P2,P3)
619         local v1,v2,v3,v4,v5,shape
620         if #bbs==3 then
621                 v1=bbs[1][1];v2=bbs[2][2]
622                 shape={ boxshape(v1,v2) }
623         elseif #bb:shape()[1]==6 then
624                 v1=bbs[1][1];v3=bbs[2][2]; v4=bbs[3][2]; v5=bbs[4][2];
625                 v2=bbs[5][2];
626                 shape={ boxshape(v1,v2,v3,v4,v5) }
627         elseif #bb:shape()[1]==8 then
628                 v1=V(bbs[5][1].x,bbs[3][1].y)
629                 v2=V(bbs[1][1].x,bbs[7][1].y)
630                 shape={ boxshape(v1,v2) }
631         elseif #bb:shape()[1]==11 then
632                 v1=V(bbs[5][1].x,bbs[3][1].y)
633                 v2=V(bbs[1][1].x,bbs[7][1].y)
634                 v3=bbs[7][2]; v4=bbs[8][2]; v5=bbs[9][2];
635                 shape={ boxshape(v1,v2,v3,v4,v5) }
636         end
637
638         local obj = path(model, shape,
639                 {pen=pen, pathmode=pathmode, stroke=stroke, fill=fill})
640
641         elements[1]=obj
642         local final = ipe.Group(elements)
643         final:setMatrix(p[prim]:matrix())
644
645         local t = { label="edit box", pno=model.pno, vno=model.vno,
646                                         layer=p:active(model.vno),
647                                         original=p[prim]:clone(),
648                                         primary=prim, final=final }
649         t.undo = function (t, doc)
650                                  doc[t.pno]:replace(t.primary, t.original)
651                          end
652         t.redo = function (t, doc)
653                                  doc[t.pno]:replace(t.primary, t.final)
654                          end
655         model:register(t)
656 end
657
658 -- Edit a group object
659 local function action_edit_group(model,prim,obj)
660         local otype,values,pos=parse_group_values(model,prim)
661         if otype==UNKNOWN then
662                 model:warning("Cannot edit this object")
663                 return
664         elseif otype==TABBED_TEXT then
665                 local newvalues=edit_tabbed_values(model, values)
666                 if not newvalues then return end
667                 if newvalues.htext=="" then
668                         newvalues.rounded=true
669                         create_boxed(model,newvalues,pos,prim)
670                 else
671                         create_tabbed(model,newvalues,pos,prim)
672                 end
673         elseif otype==BOXED_OTHER or otype==BOXED_TEXT then
674                 edit_box(model,prim)
675         end
676 end
677
678 -- saving the old function
679 function _G.MODEL:presentation_backup_actinon_edit () end
680 _G.MODEL.presentation_backup_action_edit = _G.MODEL.action_edit
681
682 -- modify the global edit action
683 function _G.MODEL:action_edit()
684         local p = self:page()
685         local prim = p:primarySelection()
686         if not prim then
687            self:presentation_backup_action_edit()
688            return 
689         end
690         local obj = p[prim]
691         if obj:type() == "group" then
692            action_edit_group(self, prim, obj)
693         else
694            self:presentation_backup_action_edit()
695         end
696 end
697
698 -- Run to create a new object
699 function tabbedboxed(model)
700         local values=edit_tabbed_values(model)
701         if not values then return end
702         if values.htext=="" then
703                 values.rounded=true
704                 create_boxed(model,values)
705         else
706                 create_tabbed(model,values)
707         end
708 end
709
710 -- deselect all selected
711 function deselectAll(model)
712         local doc = model.doc
713         for i,p in doc:pages() do
714                 p:deselectAll()
715         end
716 end
717
718 -- box the selected objects
719 function boxit(model)
720         local p=model:page()
721         local box = ipe.Rect()
722         local elements={0}
723         for i,obj,sel,layer in p:objects() do
724                 if sel then
725                         box:add(p:bbox(i))
726                         elements[#elements+1]=obj:clone()
727                 end
728         end
729         if #elements==1 then
730                 model.ui:explain('No selection to box')
731                 return
732         end
733         local s=8
734         local layout = model.doc:sheets():find("layout")
735 --      local maxx=layout.framesize.x
736
737         local x1=box:bottomLeft().x-s
738 --      if x1 < 0 then x1 = 0 end
739         local y1=box:bottomLeft().y-s
740
741         local x2=box:topRight().x+s
742 --      if x2 > maxx then x2 = maxx end
743         local y2=box:topRight().y+s
744
745         local d=box_property_dialog(model)
746
747         local colors = model.doc:sheets():allNames("color")
748         local pens= model.doc:sheets():allNames("pen")
749         local pathmodes = {"stroked", "strokedfilled", "filled"}
750
751         -- default values
752         d:set("rounded", true)
753
754         d:set("pathmode", indexOf(PREFERRED_BOX_MODE,pathmodes))
755
756         if indexOf("box_border",colors) then
757                 d:set("stroke", indexOf("box_border",colors))
758         elseif model.attributes.stroke then
759                 d:set("stroke", indexOf(model.attributes.stroke,colors) )
760         end
761
762         if indexOf("box_fill",colors) then
763                 d:set("fill", indexOf("box_fill",colors))
764         elseif model.attributes.fill then
765                 d:set("fill", indexOf(model.attributes.fill,colors) )
766         end
767         
768         if indexOf("boxborder",pens) then
769                 d:set("pen", indexOf("boxborder",pens))
770         elseif model.attributes.pen then
771                 d:set("pen", indexOf(model.attributes.pen,pens) )
772         end
773
774         local r = d:execute(BOX_DIALOG_SIZE)
775
776         if not r then return end
777         local pathmode=pathmodes[d:get("pathmode")]
778         local stroke=colors[d:get("stroke")]
779         local fill=colors[d:get("fill")]
780         local pen=pens[d:get("pen")]
781
782         local boxshape
783         if d:get('rounded') and d:get('pointer') then
784                 boxshape=boxshape_round_pointer
785         elseif d:get('rounded') and not d:get('pointer') then
786                 boxshape=boxshape_round
787         elseif not d:get('rounded') and d:get('pointer') then
788                 boxshape=boxshape_square_pointer
789         else
790                 boxshape=boxshape_square
791         end
792
793         local shape = { boxshape(V(x1,y1), V(x2,y2)) }
794
795         local obj = path(model, shape, {pen=pen, pathmode=pathmode, stroke=stroke, fill=fill})
796
797         elements[1]=obj
798         local final = ipe.Group(elements)
799
800         local t = { label="add box", pno=model.pno, vno=model.vno,
801                                         layer=p:active(model.vno), object=obj,
802                                         selection=model:selection(),
803                                         undo=_G.revertOriginal,
804                                         original=p:clone(),
805                                         final=final }
806         t.redo = function (t, doc)
807                                  local p = doc[t.pno]
808                                  for i = #t.selection,1,-1 do p:remove(t.selection[i]) end
809                                  p:insert(nil, t.final, 1, t.layer)
810                          end
811         model:register(t)
812 end
813
814 methods = {
815   { label = "Box It", run=boxit},
816   { label = "Tabbed/Boxed Text", run=tabbedboxed},
817   { label = "Deselect All", run=deselectAll},
818 }
819