Make sure all resources are set
[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:runLatex() -- Necessary to re-set the resources!
450                 model:register(t)
451         else
452     model:runLatex() -- Necessary to re-set the resources!
453                 model:creation("create boxed text", obj)
454         end
455 end
456
457 -- Create the requested object from values
458 local function create_tabbed(model,values, pos, prim)
459         local fs = model.doc:sheets():find("layout").framesize
460         local p = model:page()
461         local editmode=(prim~=nil)
462
463         local width fwidth=tonumber(values.fwidth)
464
465         if not fwidth or fwidth<0 or fwidth>1 then
466                 width=fs.x
467                 fwidth=1
468         else
469                 width=fwidth*fs.x
470         end
471
472         -- spacing
473         local s=Spacing[values.size]
474         local h=Height[values.size]
475         if not s or not h then
476                 init_spacing(model)
477                 s=Spacing[values.size]
478                 h=Height[values.size]
479         end
480
481         local bheight=measure_height(model,values.btext,values.size,width-2*s)
482
483         -- location
484         if not pos then
485                 x1=fs.x/2-width/2
486                 x2=fs.x/2+width/2
487                 y2=fs.y/2
488                 y1=y2-h-bheight-3.8*s
489         else
490                 x1=pos.x
491                 x2=x1+width
492                 y2=pos.y
493                 y1=y2-h-bheight-3.8*s
494         end
495         if fwidth>.99 then x1,x2=0,fs.x end
496
497         -- header text
498         pos=V(x1+s, y2-s)
499         local ht= ipe.Text(model.attributes, values.htext, pos, width-2*s)
500         ht:set('stroke', 'white')
501         ht:set('textsize', values.size)
502
503         -- body text
504         pos=V(x1+s, y2-s-h-2*s)
505         local bt= ipe.Text(model.attributes, values.btext, pos, width-2*s)
506         bt:set('textsize', values.size)
507
508         -- header box
509         local shape1 = { boxshape_roundTR(V(x1,y2-h-2*s), V(x2,y2)) }
510         local hb = path(model, shape1,
511                         {pathmode='filled', fill=values.hcolor, stroke="white"})
512         hb:set('pathmode', 'filled', "white", values.hcolor)
513
514         -- body box
515         local shape2 = { boxshape_roundLL(V(x1,y1), V(x2,y2-h-2*s)) }
516         local bb = path(model, shape2,
517                 {pathmode='filled', fill=values.bcolor, stroke="white"})
518
519         -- group object
520         local elements={hb,bb,ht,bt}
521         local obj=ipe.Group(elements)
522         if values.pinned then obj:set('pinned', 'horizontal') end
523
524         if editmode then
525                 local t={original=p[prim]:clone(),
526                                 label="edit tabbed text",
527                                 pno=model.pno,
528                                 vno=model.vno,
529                                 primary=prim,
530                                 final=obj }
531                 t.undo = function (t, doc)
532                                          doc[t.pno]:replace(t.primary, t.original)
533                                  end
534                 t.redo = function (t, doc)
535                                          doc[t.pno]:replace(t.primary, t.final)
536                                  end
537     model:runLatex() -- Necessary to re-set the resources!
538                 model:register(t)
539         else
540     model:runLatex() -- Necessary to re-set the resources!
541                 model:creation("create tabbed text", obj)
542                 -- model.doc:runLatex() -- may crash the thing
543         end
544
545 end
546
547 -- create the dialog for editing box properties
548 local function box_property_dialog(model)
549         local colors = model.doc:sheets():allNames("color")
550         local pens= model.doc:sheets():allNames("pen")
551         local pathmodes = {"stroked", "strokedfilled", "filled"}
552         local d = ipeui.Dialog(mainWindow(model), "Edit box properties")
553
554         d:add("rounded", "checkbox", { label="Round Corner"}, 1, 1, 1, 1)
555         d:add("pointer", "checkbox", { label="Pointer"}, 1, 2, 1, 1)
556         d:add("mlabel", "label", { label="Mode"}, 2, 1)
557         d:add("pathmode", "combo", pathmodes, 2, 2)
558         d:add("flabel", "label", { label="Fill Color" }, 3, 1)
559         d:add("fill", "combo", colors, 3, 2)
560         d:add("slabel", "label", { label="Stroke Color" }, 4, 1)
561         d:add("stroke", "combo", colors, 4, 2)
562         d:add("plabel", "label", { label="Line Width"}, 5, 1)
563         d:add("pen", "combo", pens, 5, 2)
564         d:add("cancel", "button", { label="&Cancel", action="reject" }, 6, 1)
565         d:add("ok", "button", { label="&Ok", action="accept" }, 6, 2)
566         d:setStretch("column", 2, 1)
567         return d
568 end
569
570 -- edit a box object
571 local function edit_box(model, prim)
572         local colors = model.doc:sheets():allNames("color")
573         local pens= model.doc:sheets():allNames("pen")
574         local pathmodes = {"stroked", "strokedfilled", "filled"}
575
576         local p=model:page()
577         local elements=p[prim]:elements()
578         local bb=elements[1]
579         local bbs=bb:shape()[1]
580
581         local d=box_property_dialog(model)
582
583         -- default values
584         d:set("rounded", #bbs>=7)
585         d:set("pathmode", indexOf(bb:get('pathmode'),pathmodes))
586         d:set("pointer", #bbs==6 or #bbs==11)
587         if indexOf(bb:get('stroke'),colors) then
588                 d:set("stroke", indexOf(bb:get('stroke'),colors) )
589         elseif not model.attributes.stroke then
590                 d:set("stroke", indexOf(model.attributes.stroke,colors) )
591         end
592         if indexOf(bb:get('fill'),colors) then
593                 d:set("fill", indexOf(bb:get('fill'),colors) )
594         elseif not model.attributes.fill then
595                 d:set("fill", indexOf(model.attributes.fill,colors) )
596         end
597         if indexOf(bb:get('pen'),pens) then
598                 d:set("pen", indexOf(bb:get('pen'),pens) )
599         elseif not model.attributes.pen then
600                 d:set("pen", indexOf(model.attributes.pen,pens) )
601         end
602
603         local r = d:execute(BOX_DIALOG_SIZE)
604         if not r then return end
605         local pathmode=pathmodes[d:get("pathmode")]
606         local stroke=colors[d:get("stroke")]
607         local fill=colors[d:get("fill")]
608         local pen=pens[d:get("pen")]
609
610         local boxshape
611         if d:get('rounded') and d:get('pointer') then
612                 boxshape=boxshape_round_pointer
613         elseif d:get('rounded') and not d:get('pointer') then
614                 boxshape=boxshape_round
615         elseif not d:get('rounded') and d:get('pointer') then
616                 boxshape=boxshape_square_pointer
617         else
618                 boxshape=boxshape_square
619         end
620
621         -- v1=BL, v2=TR, v3=P1, v4=P2, v5=P3. Pointer=(P1,P2,P3)
622         local v1,v2,v3,v4,v5,shape
623         if #bbs==3 then
624                 v1=bbs[1][1];v2=bbs[2][2]
625                 shape={ boxshape(v1,v2) }
626         elseif #bb:shape()[1]==6 then
627                 v1=bbs[1][1];v3=bbs[2][2]; v4=bbs[3][2]; v5=bbs[4][2];
628                 v2=bbs[5][2];
629                 shape={ boxshape(v1,v2,v3,v4,v5) }
630         elseif #bb:shape()[1]==8 then
631                 v1=V(bbs[5][1].x,bbs[3][1].y)
632                 v2=V(bbs[1][1].x,bbs[7][1].y)
633                 shape={ boxshape(v1,v2) }
634         elseif #bb:shape()[1]==11 then
635                 v1=V(bbs[5][1].x,bbs[3][1].y)
636                 v2=V(bbs[1][1].x,bbs[7][1].y)
637                 v3=bbs[7][2]; v4=bbs[8][2]; v5=bbs[9][2];
638                 shape={ boxshape(v1,v2,v3,v4,v5) }
639         end
640
641         local obj = path(model, shape,
642                 {pen=pen, pathmode=pathmode, stroke=stroke, fill=fill})
643
644         elements[1]=obj
645         local final = ipe.Group(elements)
646         final:setMatrix(p[prim]:matrix())
647
648         local t = { label="edit box", pno=model.pno, vno=model.vno,
649                                         layer=p:active(model.vno),
650                                         original=p[prim]:clone(),
651                                         primary=prim, final=final }
652         t.undo = function (t, doc)
653                                  doc[t.pno]:replace(t.primary, t.original)
654                          end
655         t.redo = function (t, doc)
656                                  doc[t.pno]:replace(t.primary, t.final)
657                          end
658         model:register(t)
659 end
660
661 -- Edit a group object
662 local function action_edit_group(model,prim,obj)
663         local otype,values,pos=parse_group_values(model,prim)
664         if otype==UNKNOWN then
665                 model:warning("Cannot edit this object")
666                 return
667         elseif otype==TABBED_TEXT then
668                 local newvalues=edit_tabbed_values(model, values)
669                 if not newvalues then return end
670                 if newvalues.htext=="" then
671                         newvalues.rounded=true
672                         create_boxed(model,newvalues,pos,prim)
673                 else
674                         create_tabbed(model,newvalues,pos,prim)
675                 end
676         elseif otype==BOXED_OTHER or otype==BOXED_TEXT then
677                 edit_box(model,prim)
678         end
679 end
680
681 -- saving the old function
682 function _G.MODEL:presentation_backup_actinon_edit () end
683 _G.MODEL.presentation_backup_action_edit = _G.MODEL.action_edit
684
685 -- modify the global edit action
686 function _G.MODEL:action_edit()
687         local p = self:page()
688         local prim = p:primarySelection()
689         if not prim then
690            self:presentation_backup_action_edit()
691            return
692         end
693         local obj = p[prim]
694         if obj:type() == "group" then
695            action_edit_group(self, prim, obj)
696         else
697            self:presentation_backup_action_edit()
698         end
699 end
700
701 -- Run to create a new object
702 function tabbedboxed(model)
703         local values=edit_tabbed_values(model)
704         if not values then return end
705         if values.htext=="" then
706                 values.rounded=true
707                 create_boxed(model,values)
708         else
709                 create_tabbed(model,values)
710         end
711 end
712
713 -- deselect all selected
714 function deselectAll(model)
715         local doc = model.doc
716         for i,p in doc:pages() do
717                 p:deselectAll()
718         end
719 end
720
721 -- box the selected objects
722 function boxit(model)
723         local p=model:page()
724         local box = ipe.Rect()
725         local elements={0}
726         for i,obj,sel,layer in p:objects() do
727                 if sel then
728                         box:add(p:bbox(i))
729                         elements[#elements+1]=obj:clone()
730                 end
731         end
732         if #elements==1 then
733                 model.ui:explain('No selection to box')
734                 return
735         end
736         local s=8
737         local layout = model.doc:sheets():find("layout")
738 --      local maxx=layout.framesize.x
739
740         local x1=box:bottomLeft().x-s
741 --      if x1 < 0 then x1 = 0 end
742         local y1=box:bottomLeft().y-s
743
744         local x2=box:topRight().x+s
745 --      if x2 > maxx then x2 = maxx end
746         local y2=box:topRight().y+s
747
748         local d=box_property_dialog(model)
749
750         local colors = model.doc:sheets():allNames("color")
751         local pens= model.doc:sheets():allNames("pen")
752         local pathmodes = {"stroked", "strokedfilled", "filled"}
753
754         -- default values
755         d:set("rounded", true)
756
757         d:set("pathmode", indexOf(PREFERRED_BOX_MODE,pathmodes))
758
759         if indexOf("box_border",colors) then
760                 d:set("stroke", indexOf("box_border",colors))
761         elseif model.attributes.stroke then
762                 d:set("stroke", indexOf(model.attributes.stroke,colors) )
763         end
764
765         if indexOf("box_fill",colors) then
766                 d:set("fill", indexOf("box_fill",colors))
767         elseif model.attributes.fill then
768                 d:set("fill", indexOf(model.attributes.fill,colors) )
769         end
770
771         if indexOf("boxborder",pens) then
772                 d:set("pen", indexOf("boxborder",pens))
773         elseif model.attributes.pen then
774                 d:set("pen", indexOf(model.attributes.pen,pens) )
775         end
776
777         local r = d:execute(BOX_DIALOG_SIZE)
778
779         if not r then return end
780         local pathmode=pathmodes[d:get("pathmode")]
781         local stroke=colors[d:get("stroke")]
782         local fill=colors[d:get("fill")]
783         local pen=pens[d:get("pen")]
784
785         local boxshape
786         if d:get('rounded') and d:get('pointer') then
787                 boxshape=boxshape_round_pointer
788         elseif d:get('rounded') and not d:get('pointer') then
789                 boxshape=boxshape_round
790         elseif not d:get('rounded') and d:get('pointer') then
791                 boxshape=boxshape_square_pointer
792         else
793                 boxshape=boxshape_square
794         end
795
796         local shape = { boxshape(V(x1,y1), V(x2,y2)) }
797
798         local obj = path(model, shape, {pen=pen, pathmode=pathmode, stroke=stroke, fill=fill})
799
800         elements[1]=obj
801         local final = ipe.Group(elements)
802
803         local t = { label="add box", pno=model.pno, vno=model.vno,
804                                         layer=p:active(model.vno), object=obj,
805                                         selection=model:selection(),
806                                         undo=_G.revertOriginal,
807                                         original=p:clone(),
808                                         final=final }
809         t.redo = function (t, doc)
810                                  local p = doc[t.pno]
811                                  for i = #t.selection,1,-1 do p:remove(t.selection[i]) end
812                                  p:insert(nil, t.final, 1, t.layer)
813                          end
814         model:register(t)
815 end
816
817 methods = {
818   { label = "Box It", run=boxit},
819   { label = "Tabbed/Boxed Text", run=tabbedboxed},
820   { label = "Deselect All", run=deselectAll},
821 }