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