From b58d153b0959a8ae0ff61c3869bd6e6f83686f9a Mon Sep 17 00:00:00 2001 From: =?utf8?q?Thomas=20Bl=C3=A4sius?= Date: Wed, 11 Sep 2013 17:20:42 +0200 Subject: [PATCH] initially adding the graph and presentation ipelets --- ipelets/graph/graph.lua | 277 +++++++++ ipelets/presentation/presentation.lua | 801 ++++++++++++++++++++++++++ 2 files changed, 1078 insertions(+) create mode 100644 ipelets/graph/graph.lua create mode 100644 ipelets/presentation/presentation.lua diff --git a/ipelets/graph/graph.lua b/ipelets/graph/graph.lua new file mode 100644 index 0000000..52eb188 --- /dev/null +++ b/ipelets/graph/graph.lua @@ -0,0 +1,277 @@ +---------------------------------------------------------------------- +-- graph ipelet +---------------------------------------------------------------------- +label = "Graph" + +about = [[ Some features making it easier to work with graphs. ]] + +local deactivateGraphMode = false + +function toggleGraphMode () + if deactivateGraphMode then + deactivateGraphMode = false + else + deactivateGraphMode = true + end +end + +local editing = false +local currMarkId = nil + +-------------------------------------------------------------------------------- +-- add an edit action for marks ------------------------------------------------ + +-- saving the old function +function _G.MODEL:graph_backup_actinon_edit () end +_G.MODEL.graph_backup_action_edit = _G.MODEL.action_edit + +-- adding support for objects of type reference +function _G.MODEL:action_edit() + if deactivateGraphMode then + self:graph_backup_action_edit() + return + end + local p = self:page() + local prim = p:primarySelection() + if not prim then + self:graph_backup_action_edit() + return + end + local obj = p[prim] + if obj:type() == "reference" then + action_edit_reference (self, prim, obj) + else + self:graph_backup_action_edit() + end +end + + +-- starting to edit a mark +function action_edit_reference(model, prim, obj) + editing = true + currMarkId = prim + local p = model:page() + + local pos = obj:matrix() * obj:position() + -- print(pos) + + -- creating a circle at the position of the mark + local ellipse = {type="ellipse"} + ellipse[1] = ipe.Matrix({10, 0, 0, 10, pos.x, pos.y}) + -- print(ellipse[1]) + local circ = ipe.Path(model.attributes, {ellipse}) + p:insert(nil, circ, 0, p:layerOf(prim)) + + -- edit the circle instead of the mark itself + model:action_edit_path(#p, circ) + + -- print("test") +end + +-------------------------------------------------------------------------------- +-- pressing a key while editing the cycle -------------------------------------- + +-- saving old function +function _G.EDITTOOL:graph_backup_key(code, modifiers, text) end +_G.EDITTOOL.graph_backup_key = _G.EDITTOOL.key + +-- overwriting +function _G.EDITTOOL:key(code, modifiers, text) + self:graph_backup_key(code, modifiers, text) + if deactivateGraphMode then return end + + -- react if and only if we are currently editing a mark and key ESC + -- or SPACE is pressed + if text ~= "\027" and code ~= 0x20 then return end + if not editing then return end + + editing = false + + -- finding new and old position + local p = self.model:page() + local circ = p[#p] + local mark = p[currMarkId] + local oldPos = mark:matrix() * mark:position() + local newPos = circ:shape()[1][1]:translation() + + -- remove the intermediate step of moving the cycle from the undo + -- stack and remove the cycle itself + local undoSt = self.model.undo + p:remove(#p) + table.remove(undoSt) + + -- new action for the undo stack moving the mark and all endpoints + -- ending at the mark + local t = { label = "edit reference", + pno = self.model.pno, + vno = self.model.vno, + selection = self.model:selection(), + original = self.model:page():clone(), + matrix = matrix, + undo = _G.revertOriginal,} + t.redo = function (t, doc) + p:transform(currMarkId, ipe.Translation(newPos-oldPos)) + moveEndpoints(oldPos, newPos, p) + end + self.model:register(t) +end + +-- function moving all endpoints and intermediate points in polylines +-- to newPos, if the squared distance to oldPos is at most sqEps +local sqEps = 1 +function moveEndpoints(oldPos, newPos, p) + for i, obj, sel, layer in p:objects() do + if obj:type() == "path" then + local shape = obj:shape() + for _, subPath in ipairs(shape) do + if (subPath["type"] == "curve") then + for _,seg in ipairs(subPath) do + if (seg["type"] == "segment") then + for j, point in ipairs(seg) do + -- print(j, point, oldPos) + if (obj:matrix() * point - oldPos):sqLen() < sqEps then + seg[j] = obj:matrix():inverse() * newPos + -- print("test", seg[j]) + end + end + elseif (seg["type"] == "spline") then + if (obj:matrix() * seg[1] - oldPos):sqLen() < sqEps then + seg[1] = obj:matrix():inverse() * newPos + end + if (obj:matrix() * seg[#seg] - oldPos):sqLen() < sqEps then + seg[#seg] = obj:matrix():inverse() * newPos + end + end + end + end + obj:setShape(shape) + end + end + end +end + + +-------------------------------------------------------------------------------- +-- working with groups --------------------------------------------------------- + +local function regroup(elem) + local groupElem = {} + for i, obj in ipairs(elem) do + if obj[1] ~= nil then + groupElem[#groupElem + 1] = regroup(obj) + else + groupElem[#groupElem + 1] = obj + end + end + return ipe.Group(groupElem) +end + +local function ungroup(group) + local elem = group:elements() + local plainElem = {} + for i, obj in ipairs(elem) do + if (obj:type() == "group") then + local subElem, subPlainElem = ungroup(obj) + elem[i] = subElem; + for _, subObj in ipairs(subPlainElem) do + table.insert(plainElem, subObj) + end + else + table.insert(plainElem, obj) + end + end + return elem, plainElem +end + +-------------------------------------------------------------------------------- +-- shorten paths --------------------------------------------------------------- + +function shortenObj(obj, lenSource, lenTarget) + if obj:type() == "path" then + local shape = obj:shape() + for _, subPath in ipairs(shape) do + local first = subPath[1] + local last = subPath[#subPath] + + local p1 = obj:matrix() * first[1] + local p2 = obj:matrix() * first[2] + local pDelta = p2 - p1 + local pNorm = pDelta:normalized() + local newP1 = p1 + pNorm*lenSource + + local q1 = obj:matrix() * last[#last] + local q2 = obj:matrix() * last[#last - 1] + local qDelta = q2 - q1 + local qNorm = qDelta:normalized() + local newQ1 = q1 + qNorm*lenTarget + + first[1] = obj:matrix():inverse() * newP1 + last[#last] = obj:matrix():inverse() * newQ1 + end + obj:setShape(shape) + end +end + +function getString(model, string) + if ipeui.getString ~= nil then + return ipeui.getString(model.ui, "Enter length") + else + return model:getString("Enter length") + end +end + +function shorten(model, num) + num = num - 1 + local lenTarget = 0 + local lenSource = 0 + -- local str = ipeui.getString(model.ui, "Enter length") + -- local str = model:getString("Enter length") + local str = getString(model, "Enter length") + if not str or str:match("^%s*$)") then return end + if num == 1 then -- shorten target + lenTarget = tonumber(str) + elseif num == 2 then -- shorten source + lenSource = tonumber(str) + elseif num == 3 then -- shorten both + lenTarget = tonumber(str) + lenSource = tonumber(str) + end + + -- start to edit the edges + local t = { label = "shorten edges", + pno = model.pno, + vno = model.vno, + selection = model:selection(), + original = model:page():clone(), + matrix = matrix, + undo = _G.revertOriginal,} + t.redo = function (t, doc) + local p = doc[t.pno] + for _, i in ipairs(t.selection) do + p:setSelect(i, 2) + end + local p = doc[t.pno] + for i, obj, sel, layer in p:objects() do + if sel and obj:type() == "group" then + local elem, plainElem = ungroup(obj) + for _,subobj in pairs(plainElem) do + shortenObj(subobj, lenSource, lenTarget) + end + p:replace(i, regroup(elem)) + elseif sel then + shortenObj(obj, lenSource, lenTarget) + end + end + end + model:register(t) +end + + +methods = { + { label = "toggle graph mode", run=toggleGraphMode }, + { label = "shorten target", run=shorten }, + { label = "shorten source", run=shorten }, + { label = "shorten both", run=shorten }, +} + +---------------------------------------------------------------------- diff --git a/ipelets/presentation/presentation.lua b/ipelets/presentation/presentation.lua new file mode 100644 index 0000000..93516be --- /dev/null +++ b/ipelets/presentation/presentation.lua @@ -0,0 +1,801 @@ +---------------------------------------------------------------------- +-- presentation goodies for Ipe + +--[[ + +SUMMARY + + This ipelet adds a few goodies for presentation, including + 1. Ability to create beamer-like boxes with/without header + 2. Add a framed box for the selected objects (including text) + 3. A function to deselect all selected on all pages + 4. The boxes can be edited as text/path objects (press E) + 5. A few items can be added to the style sheet to add preferred + symbolics for box colors etc. This has the benefit that this + preferences can be changed to affect all boxes (see below). + + The design of these boxes is from Martin Nöllenburg's presentation + example posted on the Ipe7 wiki page. + +STYLESHEET (CHANGING PREFERRED SETTINGS) + + Example style sheet to cascade with presentation.isy is as follows. + +---- content of example.isy --- +example removed, not possible to upload this file to the ipe-wiki otherwise +---- end content of example.isy --- + + where: + tab_header= color of the tab header in a tabbed box + tab_body = color of the tab body in a tabbed box + box_fill = fill color a box + box_border= color of the box border + boxborder = linewidth of the box border + + The preferred box mode (stroked/filled/strokedfilled) can be changed by + changing the hard-wired value (no stylesheet option) PREFERRED_BOX_MODE below + + With the above style sheet, one can start an empty presentation using + ipe -sheet presentation -sheet /path/to/example.isy + +SHORTCUT + + Shortcuts to these functions can be changed as for other ipelets: + shortcuts.ipelet_x_presentation = "Key" + where x is the index (starting 1) of the sub-menu for the function + +FILE/AUTHOR HISTORY + + version 0. Initial Release. Zhengdao Wang 2010 + version 1. add a line here if a change is made + +LICENSE + + This file can be distributed and modified under the terms of the GNU General + Public License as published by the Free Software Foundation; either version + 3, or (at your option) any later version. + + This file is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + details. + +--]] + +---------------------------------------------------------------------- + +label = "Presentation" + +about = [[ + Presentation Goodies: Add boxes around objects, deselect all, + add tabbed/boxed text. +]] + +V = ipe.Vector +indexOf=_G.indexOf + +-- table storing the height of the first line box +Height={} +Spacing={} +Has_Height=nil + +local c=10 -- corner size + +local UNKNOWN=0 +local TABBED_TEXT=1 +local BOXED_TEXT=2 +local BOXED_OTHER=3 + +local PREFERRED_BOX_MODE="strokedfilled" -- or stroked or filled + +local BOX_DIALOG_SIZE={400,140} + +-- initialize the Height table +local function init_spacing(model) + local p=model:page() + local xx=300 + local iH={} + local sizes = model.doc:sheets():allNames("textsize") + for i,size in ipairs(sizes) do + obj=ipe.Text({textsize=size, minipage=true}, "ABC\n\nDEF", V(xx,xx), xx) + local layer=p:active(model.vno) + p:insert(nil, obj, nil, layer) + obj=ipe.Text({textsize=size, minipage=true}, "ABC", V(xx,xx), xx) + p:insert(nil, obj, nil, layer) + iH[#iH+1]=size + end + model.doc:runLatex() + for i=#sizes,1,-1 do + Height[iH[i]]=xx-p:bbox(#p):bottomLeft().y + p:remove(#p) + Spacing[iH[i]]=(xx-p:bbox(#p):bottomLeft().y)/2-Height[iH[i]] + p:remove(#p) + end + Has_Height=1 +end + +-- generate a path with given properties +local function path(model, shape, props) + local oldvalues={} + for k,v in pairs(props) do + oldvalues[k]=model.attributes[k] + model.attributes[k]=v + end + local obj = ipe.Path(model.attributes, shape) + for k,v in pairs(props) do + model.attributes[k]=oldvalues[k] + end + if props.pen then obj:set('pen', props.pen) end + obj:set('pathmode', props.pathmode, props.stroke, props.fill) + return obj +end + +-- create a square box #shape=3 +local function boxshape_square(v1, v2) + return { type="curve", closed=true; + { type="segment"; v1, V(v1.x, v2.y) }, -- L + { type="segment"; V(v1.x, v2.y), v2 }, -- T + { type="segment"; v2, V(v2.x, v1.y) } } -- R +end + +-- create a square box with a pointer #shape=6 +local function boxshape_square_pointer(v1, v2, v3, v4, v5) + local dx=v2.x-v1.x + local dy=v2.y-v1.y + local v3=v3 or V(v1.x+.4*dx, v2.y) + local v4=v4 or V(v1.x+.6*dx,v2.y+.3*dy) + local v5=v5 or V(v1.x+.5*dx,v2.y) + return { type="curve", closed=true; + { type="segment"; v1, V(v1.x, v2.y) }, -- L + { type="segment"; V(v1.x, v2.y), v3}, -- T + { type="segment"; v3, v4}, -- P + { type="segment"; v4, v5}, --P + { type="segment"; v5, v2}, -- T + { type="segment"; v2, V(v2.x, v1.y) } } -- R +end + +-- create a header box: round corner on topRight +local function boxshape_roundTR(v1, v2) + return { type="curve", closed=true; + { type="segment"; v1, V(v1.x, v2.y) }, + { type="segment"; V(v1.x, v2.y), V(v2.x-c, v2.y) }, + { type="bezier"; V(v2.x-c, v2.y), V(v2.x-c/2, v2.y), + V(v2.x, v2.y-c/2), V(v2.x, v2.y-c) }, + { type="segment"; V(v2.x, v2.y-c), V(v2.x, v1.y) } } +end + +-- create a body box: round corner on bottom Left +local function boxshape_roundLL(v1, v2) + return { type="curve", closed=true; + { type="segment"; v2, V(v2.x, v1.y) }, + { type="segment"; V(v2.x, v1.y), V(v1.x+c, v1.y) }, + { type="bezier"; V(v1.x+c, v1.y), V(v1.x+c/2,v1.y), + V(v1.x, v1.y+c/2), V(v1.x, v1.y+c) }, + { type="segment"; V(v1.x, v1.y+c), V(v1.x, v2.y) } } +end + +-- create a body box: 4 round corners #shape=8 +local function boxshape_round(v1, v2) + return { type="curve", closed=true; + { type="segment"; V(v2.x, v2.y-c), V(v2.x, v1.y+c) }, -- R + { type="bezier"; V(v2.x,v1.y+c), V(v2.x, v1.y+c/2), + V(v2.x-c/2,v1.y), V(v2.x-c,v1.y)}, -- BR + { type="segment"; V(v2.x-c, v1.y), V(v1.x+c, v1.y) }, -- B + { type="bezier"; V(v1.x+c, v1.y), V(v1.x+c/2,v1.y), + V(v1.x, v1.y+c/2), V(v1.x, v1.y+c) }, -- BL + { type="segment"; V(v1.x, v1.y+c), V(v1.x, v2.y-c) }, -- L + { type="bezier"; V(v1.x, v2.y-c), V(v1.x,v2.y-c/2), + V(v1.x+c/2, v2.y), V(v1.x+c, v2.y) }, -- TL + { type="segment"; V(v1.x+c, v2.y), V(v2.x-c, v2.y) }, -- T + { type="bezier"; V(v2.x-c, v2.y), V(v2.x-c/2,v2.y), + V(v2.x, v2.y-c/2), V(v2.x, v2.y-c) }, -- TR + } +end + +-- create a body box: 4 round corners, with pointer #shape=11 +local function boxshape_round_pointer(v1, v2, v3, v4, v5) + local dx=v2.x-v1.x + local dy=v2.y-v1.y + local v3=v3 or V(v1.x+.4*dx, v2.y) + local v4=v4 or V(v1.x+.6*dx,v2.y+.3*dy) + local v5=v5 or V(v1.x+.5*dx,v2.y) + return { type="curve", closed=true; + { type="segment"; V(v2.x, v2.y-c), V(v2.x, v1.y+c) }, -- R + { type="bezier"; V(v2.x,v1.y+c), V(v2.x, v1.y+c/2), + V(v2.x-c/2,v1.y), V(v2.x-c,v1.y)}, -- BR + { type="segment"; V(v2.x-c, v1.y), V(v1.x+c, v1.y) }, -- B + { type="bezier"; V(v1.x+c, v1.y), V(v1.x+c/2,v1.y), + V(v1.x, v1.y+c/2), V(v1.x, v1.y+c) }, -- BL + { type="segment"; V(v1.x, v1.y+c), V(v1.x, v2.y-c) }, -- L + { type="bezier"; V(v1.x, v2.y-c), V(v1.x,v2.y-c/2), + V(v1.x+c/2, v2.y), V(v1.x+c, v2.y) }, -- TL + { type="segment"; V(v1.x+c, v2.y), v3}, -- T + { type="segment"; v3, v4}, -- P + { type="segment"; v4, v5}, --P + { type="segment"; v5, V(v2.x-c, v2.y)}, -- T + { type="bezier"; V(v2.x-c, v2.y), V(v2.x-c/2,v2.y), + V(v2.x, v2.y-c/2), V(v2.x, v2.y-c) }, -- TR + } +end + +-- parse the values from a group obj +local function parse_group_values(model,prim) + local fs = model.doc:sheets():find("layout").framesize + local p=model:page() + local bbox=p:bbox(prim) + local pos=V(bbox:bottomLeft().x, bbox:topRight().y) + + local elements=p[prim]:elements() + if #elements==4 then + local hb,bb,ht,bt=elements[1],elements[2],elements[3],elements[4] + if hb:type()=="path" and bb:type()=="path" and + ht:type()=="text" and bt:type()=="text" then + local values={htext=ht:text(), + btext=bt:text(), + pinned=(p[prim]:get("pinned")=="horizontal"), + fwidth=string.format('%.2f', + (bbox:topRight().x-bbox:bottomLeft().x)/fs.x), + hcolor=hb:get("fill"), + bcolor=bb:get("fill"), + size=ht:get("textsize")} + return TABBED_TEXT,values,pos + end + else + local bb,bt=elements[1],elements[2] + if bb:type()=="path" and #bb:shape()==1 and + bb:shape()[1].closed==true and + (#bb:shape()[1]==3 or #bb:shape()[1]==8 or + #bb:shape()[1]==6 or #bb:shape()[1]==11) then + if bt:type()=="text" then + local values={btext=bt:text(), + pinned=(p[prim]:get("pinned")=="horizontal"), + size=bt:get("textsize"), + fwidth=string.format('%.2f', + (bbox:topRight().x-bbox:bottomLeft().x)/fs.x), + bcolor=bb:get("fill")} + if #bb:shape()[1]==6 then + pos=V(pos.x,bb:shape()[1][1][2].y) + elseif #bb:shape()[1]==11 then + pos=V(pos.x,bb:shape()[1][10][2].y) + end + return BOXED_TEXT,values,pos + else + return BOXED_OTHER + end + end + end + return UNKNOWN +end + +function mainWindow(model) + if model.ui.win == nil then + return model.ui + else + return model.ui:win() + end +end + +-- Edit the values for the frame +local function edit_tabbed_values(model,values) + local d = ipeui.Dialog(mainWindow(model), "Create tabbed text") + local colors = model.doc:sheets():allNames("color") + local sizes = model.doc:sheets():allNames("textsize") + d:add("hlabel", "label", { label="Enter Header" }, 1, 1, 1, 1) + d:add("hcolor", "combo", colors, 1, 4) + d:add("htext", "input", { syntax="latex" }, 2, 1, 1, 4) + d:add("blabel", "label", { label="Enter Body"}, 3, 1, 1, 3) + d:add("bcolor", "combo", colors, 3, 4) + d:add("btext", "text", { syntax="latex" }, 4, 1, 1, 4) + d:add("size", "combo", sizes, 5, 1) + d:add("wlabel", "label", { label="width [0-1]"}, 5, 2, 1, 2) + d:add("fwidth", "input", {size=2}, 5, 3, 1, 1) + d:add("pinned", "checkbox", { label="pinned"}, 5, 4) + d:add("ok", "button", { label="&Ok", action="accept" }, 6, 4) + d:add("cancel", "button", { label="&Cancel", action="reject" }, 6, 3) + _G.addEditorField(d, "btext", 6, 2) + d:setStretch("row", 2, 1) + d:setStretch("column", 1, 1) + d:set("fwidth", "0.8") + d:set("pinned", 1) + + if indexOf("tab_header", colors) then + d:set("hcolor", indexOf("tab_header", colors)) + elseif indexOf("darkblue", colors) then + d:set("hcolor", indexOf("darkblue", colors)) + else + d:set("hcolor", indexOf("black", colors)) + end + + if indexOf("tab_body", colors) then + d:set("bcolor", indexOf("tab_body", colors)) + elseif indexOf("lightgray", colors) then + d:set("bcolor", indexOf("lightgray", colors)) + else + d:set("bcolor", indexOf("white", colors)) + end + + if values then + for k,v in pairs(values) do + if k=="hcolor" or k=="bcolor" then v=indexOf(v, colors) end + if k=="size" then v=indexOf(v, sizes) end + d:set(k, v) + end + end + + local r = d:execute(prefs.editor_size) + if not r then return end + local newvalues={} + newvalues.htext=d:get("htext") + newvalues.btext=d:get("btext") + newvalues.pinned=d:get("pinned") + newvalues.fwidth=d:get("fwidth") + newvalues.size=sizes[d:get("size")] + newvalues.hcolor=colors[d:get("hcolor")] + newvalues.bcolor=colors[d:get("bcolor")] +-- if newvalues.fwidth=="" or tonumber(newvalues.fwidth)>.99 then +-- newvalues.pinned=true +-- end + return newvalues +end + +-- measure the height a piece of given text +local function measure_height(model,text,size,width) + local p=model:page() + local obj= ipe.Text(model.attributes, text, V(0,0), width) + obj:set('textsize', size) + local layer=p:active(model.vno) + p:insert(nil, obj, nil, layer) + if not model.doc:runLatex() then + p:remove(#p) + return 100 + end + local bbox=p:bbox(#p) + p:remove(#p) + return bbox:topRight().y-bbox:bottomLeft().y +end + +-- Create boxed text +local function create_boxed(model,values, pos, prim) + local fs = model.doc:sheets():find("layout").framesize + local p = model:page() + local editmode=(prim~=nil) + + local width fwidth=tonumber(values.fwidth) + + if not fwidth or fwidth<0 or fwidth>1 then + width=fs.x + fwidth=1 + else + width=fwidth*fs.x + end + + -- spacing + local s=Spacing[values.size] + local h=Height[values.size] + if not s or not h then + init_spacing(model) + s=Spacing[values.size] + h=Height[values.size] + end + + local bheight=measure_height(model,values.btext,values.size,width-2*s) + + -- location + if not pos then + x1=fs.x/2-width/2 + x2=fs.x/2+width/2 + y2=fs.y/2 + y1=y2-bheight-1.8*s + else + x1=pos.x + x2=x1+width + y2=pos.y + y1=y2-bheight-1.8*s + end + if fwidth>.99 then x1,x2=0,fs.x end + + -- body text + pos=V(x1+s, y2-s) + local bt= ipe.Text(model.attributes, values.btext, pos, width-2*s) + bt:set('textsize', values.size) + + -- body box + local shape2 + if values.rounded then + shape2 = { boxshape_round(V(x1,y1), V(x2,y2)) } + else + shape2 = { boxshape_square(V(x1,y1), V(x2,y2)) } + end + local bb = path(model, shape2, + {pathmode='filled', fill=values.bcolor, stroke="white"}) + + -- group object + local elements={bb,bt} + local obj=ipe.Group(elements) + -- obj:setMatrix(p[prim]:matrix()) -- currently not working + if values.pinned then obj:set('pinned', 'horizontal') end + + if editmode then + local t={original=p[prim]:clone(), + label="edit boxed text", + pno=model.pno, + vno=model.vno, + primary=prim, + final=obj } + t.undo = function (t, doc) + doc[t.pno]:replace(t.primary, t.original) + end + t.redo = function (t, doc) + doc[t.pno]:replace(t.primary, t.final) + end + model:register(t) + else + model:creation("create boxed text", obj) + -- model.doc:runLatex() -- may crash the thing + end +end + +-- Create the requested object from values +local function create_tabbed(model,values, pos, prim) + local fs = model.doc:sheets():find("layout").framesize + local p = model:page() + local editmode=(prim~=nil) + + local width fwidth=tonumber(values.fwidth) + + if not fwidth or fwidth<0 or fwidth>1 then + width=fs.x + fwidth=1 + else + width=fwidth*fs.x + end + + -- spacing + local s=Spacing[values.size] + local h=Height[values.size] + if not s or not h then + init_spacing(model) + s=Spacing[values.size] + h=Height[values.size] + end + + local bheight=measure_height(model,values.btext,values.size,width-2*s) + + -- location + if not pos then + x1=fs.x/2-width/2 + x2=fs.x/2+width/2 + y2=fs.y/2 + y1=y2-h-bheight-3.8*s + else + x1=pos.x + x2=x1+width + y2=pos.y + y1=y2-h-bheight-3.8*s + end + if fwidth>.99 then x1,x2=0,fs.x end + + -- header text + pos=V(x1+s, y2-s) + local ht= ipe.Text(model.attributes, values.htext, pos, width-2*s) + ht:set('stroke', 'white') + ht:set('textsize', values.size) + + -- body text + pos=V(x1+s, y2-s-h-2*s) + local bt= ipe.Text(model.attributes, values.btext, pos, width-2*s) + bt:set('textsize', values.size) + + -- header box + local shape1 = { boxshape_roundTR(V(x1,y2-h-2*s), V(x2,y2)) } + local hb = path(model, shape1, + {pathmode='filled', fill=values.hcolor, stroke="white"}) + hb:set('pathmode', 'filled', "white", values.hcolor) + + -- body box + local shape2 = { boxshape_roundLL(V(x1,y1), V(x2,y2-h-2*s)) } + local bb = path(model, shape2, + {pathmode='filled', fill=values.bcolor, stroke="white"}) + + -- group object + local elements={hb,bb,ht,bt} + local obj=ipe.Group(elements) + if values.pinned then obj:set('pinned', 'horizontal') end + + if editmode then + local t={original=p[prim]:clone(), + label="edit tabbed text", + pno=model.pno, + vno=model.vno, + primary=prim, + final=obj } + t.undo = function (t, doc) + doc[t.pno]:replace(t.primary, t.original) + end + t.redo = function (t, doc) + doc[t.pno]:replace(t.primary, t.final) + end + model:register(t) + else + model:creation("create tabbed text", obj) + -- model.doc:runLatex() -- may crash the thing + end + +end + +-- create the dialog for editing box properties +local function box_property_dialog(model) + local colors = model.doc:sheets():allNames("color") + local pens= model.doc:sheets():allNames("pen") + local pathmodes = {"stroked", "strokedfilled", "filled"} + local d = ipeui.Dialog(mainWindow(model), "Edit box properties") + + d:add("rounded", "checkbox", { label="Round Corner"}, 1, 1, 1, 1) + d:add("pointer", "checkbox", { label="Pointer"}, 1, 2, 1, 1) + d:add("mlabel", "label", { label="Mode"}, 2, 1) + d:add("pathmode", "combo", pathmodes, 2, 2) + d:add("flabel", "label", { label="Fill Color" }, 3, 1) + d:add("fill", "combo", colors, 3, 2) + d:add("slabel", "label", { label="Stroke Color" }, 4, 1) + d:add("stroke", "combo", colors, 4, 2) + d:add("plabel", "label", { label="Line Width"}, 5, 1) + d:add("pen", "combo", pens, 5, 2) + d:add("cancel", "button", { label="&Cancel", action="reject" }, 6, 1) + d:add("ok", "button", { label="&Ok", action="accept" }, 6, 2) + d:setStretch("column", 2, 1) + return d +end + +-- edit a box object +local function edit_box(model, prim) + local colors = model.doc:sheets():allNames("color") + local pens= model.doc:sheets():allNames("pen") + local pathmodes = {"stroked", "strokedfilled", "filled"} + + local p=model:page() + local elements=p[prim]:elements() + local bb=elements[1] + local bbs=bb:shape()[1] + + local d=box_property_dialog(model) + + -- default values + d:set("rounded", #bbs>=7) + d:set("pathmode", indexOf(bb:get('pathmode'),pathmodes)) + d:set("pointer", #bbs==6 or #bbs==11) + if indexOf(bb:get('stroke'),colors) then + d:set("stroke", indexOf(bb:get('stroke'),colors) ) + elseif not model.attributes.stroke then + d:set("stroke", indexOf(model.attributes.stroke,colors) ) + end + if indexOf(bb:get('fill'),colors) then + d:set("fill", indexOf(bb:get('fill'),colors) ) + elseif not model.attributes.fill then + d:set("fill", indexOf(model.attributes.fill,colors) ) + end + if indexOf(bb:get('pen'),pens) then + d:set("pen", indexOf(bb:get('pen'),pens) ) + elseif not model.attributes.pen then + d:set("pen", indexOf(model.attributes.pen,pens) ) + end + + local r = d:execute(BOX_DIALOG_SIZE) + if not r then return end + local pathmode=pathmodes[d:get("pathmode")] + local stroke=colors[d:get("stroke")] + local fill=colors[d:get("fill")] + local pen=pens[d:get("pen")] + + local boxshape + if d:get('rounded') and d:get('pointer') then + boxshape=boxshape_round_pointer + elseif d:get('rounded') and not d:get('pointer') then + boxshape=boxshape_round + elseif not d:get('rounded') and d:get('pointer') then + boxshape=boxshape_square_pointer + else + boxshape=boxshape_square + end + + -- v1=BL, v2=TR, v3=P1, v4=P2, v5=P3. Pointer=(P1,P2,P3) + local v1,v2,v3,v4,v5,shape + if #bbs==3 then + v1=bbs[1][1];v2=bbs[2][2] + shape={ boxshape(v1,v2) } + elseif #bb:shape()[1]==6 then + v1=bbs[1][1];v3=bbs[2][2]; v4=bbs[3][2]; v5=bbs[4][2]; + v2=bbs[5][2]; + shape={ boxshape(v1,v2,v3,v4,v5) } + elseif #bb:shape()[1]==8 then + v1=V(bbs[5][1].x,bbs[3][1].y) + v2=V(bbs[1][1].x,bbs[7][1].y) + shape={ boxshape(v1,v2) } + elseif #bb:shape()[1]==11 then + v1=V(bbs[5][1].x,bbs[3][1].y) + v2=V(bbs[1][1].x,bbs[7][1].y) + v3=bbs[7][2]; v4=bbs[8][2]; v5=bbs[9][2]; + shape={ boxshape(v1,v2,v3,v4,v5) } + end + + local obj = path(model, shape, + {pen=pen, pathmode=pathmode, stroke=stroke, fill=fill}) + + elements[1]=obj + local final = ipe.Group(elements) + final:setMatrix(p[prim]:matrix()) + + local t = { label="edit box", pno=model.pno, vno=model.vno, + layer=p:active(model.vno), + original=p[prim]:clone(), + primary=prim, final=final } + t.undo = function (t, doc) + doc[t.pno]:replace(t.primary, t.original) + end + t.redo = function (t, doc) + doc[t.pno]:replace(t.primary, t.final) + end + model:register(t) +end + +-- Edit a group object +local function action_edit_group(model,prim,obj) + local otype,values,pos=parse_group_values(model,prim) + if otype==UNKNOWN then + model:warning("Cannot edit this object") + return + elseif otype==TABBED_TEXT then + local newvalues=edit_tabbed_values(model, values) + if not newvalues then return end + if newvalues.htext=="" then + newvalues.rounded=true + create_boxed(model,newvalues,pos,prim) + else + create_tabbed(model,newvalues,pos,prim) + end + elseif otype==BOXED_OTHER or otype==BOXED_TEXT then + edit_box(model,prim) + end +end + +-- saving the old function +function _G.MODEL:presentation_backup_actinon_edit () end +_G.MODEL.presentation_backup_action_edit = _G.MODEL.action_edit + +-- modify the global edit action +function _G.MODEL:action_edit() + local p = self:page() + local prim = p:primarySelection() + if not prim then + self:presentation_backup_action_edit() + return + end + local obj = p[prim] + if obj:type() == "group" then + action_edit_group(self, prim, obj) + else + self:presentation_backup_action_edit() + end +end + +-- Run to create a new object +function tabbedboxed(model) + local values=edit_tabbed_values(model) + if not values then return end + if values.htext=="" then + values.rounded=true + create_boxed(model,values) + else + create_tabbed(model,values) + end +end + +-- deselect all selected +function deselectAll(model) + local doc = model.doc + for i,p in doc:pages() do + p:deselectAll() + end +end + +-- box the selected objects +function boxit(model) + local p=model:page() + local box = ipe.Rect() + local elements={0} + for i,obj,sel,layer in p:objects() do + if sel then + box:add(p:bbox(i)) + elements[#elements+1]=obj:clone() + end + end + if #elements==1 then + model.ui:explain('No selection to box') + return + end + local s=8 + local layout = model.doc:sheets():find("layout") +-- local maxx=layout.framesize.x + + local x1=box:bottomLeft().x-s +-- if x1 < 0 then x1 = 0 end + local y1=box:bottomLeft().y-s + + local x2=box:topRight().x+s +-- if x2 > maxx then x2 = maxx end + local y2=box:topRight().y+s + + local d=box_property_dialog(model) + + local colors = model.doc:sheets():allNames("color") + local pens= model.doc:sheets():allNames("pen") + local pathmodes = {"stroked", "strokedfilled", "filled"} + + -- default values + d:set("rounded", true) + + d:set("pathmode", indexOf(PREFERRED_BOX_MODE,pathmodes)) + + if indexOf("box_border",colors) then + d:set("stroke", indexOf("box_border",colors)) + elseif model.attributes.stroke then + d:set("stroke", indexOf(model.attributes.stroke,colors) ) + end + + if indexOf("box_fill",colors) then + d:set("fill", indexOf("box_fill",colors)) + elseif model.attributes.fill then + d:set("fill", indexOf(model.attributes.fill,colors) ) + end + + if indexOf("boxborder",pens) then + d:set("pen", indexOf("boxborder",pens)) + elseif model.attributes.pen then + d:set("pen", indexOf(model.attributes.pen,pens) ) + end + + local r = d:execute(BOX_DIALOG_SIZE) + + if not r then return end + local pathmode=pathmodes[d:get("pathmode")] + local stroke=colors[d:get("stroke")] + local fill=colors[d:get("fill")] + local pen=pens[d:get("pen")] + + local boxshape + if d:get('rounded') and d:get('pointer') then + boxshape=boxshape_round_pointer + elseif d:get('rounded') and not d:get('pointer') then + boxshape=boxshape_round + elseif not d:get('rounded') and d:get('pointer') then + boxshape=boxshape_square_pointer + else + boxshape=boxshape_square + end + + local shape = { boxshape(V(x1,y1), V(x2,y2)) } + + local obj = path(model, shape, {pen=pen, pathmode=pathmode, stroke=stroke, fill=fill}) + + elements[1]=obj + local final = ipe.Group(elements) + + local t = { label="add box", pno=model.pno, vno=model.vno, + layer=p:active(model.vno), object=obj, + selection=model:selection(), + undo=_G.revertOriginal, + original=p:clone(), + final=final } + t.redo = function (t, doc) + local p = doc[t.pno] + for i = #t.selection,1,-1 do p:remove(t.selection[i]) end + p:insert(nil, t.final, 1, t.layer) + end + model:register(t) +end + +methods = { + { label = "Box It", run=boxit}, + { label = "Tabbed/Boxed Text", run=tabbedboxed}, + { label = "Deselect All", run=deselectAll}, +} + -- 2.34.1