1 ----------------------------------------------------------------------
3 ----------------------------------------------------------------------
6 about = [[ Some features making it easier to work with graphs. ]]
8 local deactivateGraphMode = false
9 local moveInvisibleObjects = false
11 function toggleGraphMode ()
12 if deactivateGraphMode then
13 deactivateGraphMode = false
15 deactivateGraphMode = true
19 function toggleMoveInvisible ()
20 if moveInvisibleObjects then
21 moveInvisibleObjects = false
23 moveInvisibleObjects = true
28 local currMarkId = nil
30 --------------------------------------------------------------------------------
31 -- add an edit action for marks ------------------------------------------------
33 -- saving the old function
34 function _G.MODEL:graph_backup_actinon_edit () end
35 _G.MODEL.graph_backup_action_edit = _G.MODEL.action_edit
37 -- adding support for objects of type reference
38 function _G.MODEL:action_edit()
39 if deactivateGraphMode then
40 self:graph_backup_action_edit()
44 local prim = p:primarySelection()
46 self:graph_backup_action_edit()
50 if obj:type() == "reference" then
51 action_edit_reference (self, prim, obj)
53 self:graph_backup_action_edit()
58 -- starting to edit a mark
59 function action_edit_reference(model, prim, obj)
62 local p = model:page()
64 local pos = obj:matrix() * obj:position()
67 -- creating a circle at the position of the mark
68 local ellipse = {type="ellipse"}
69 ellipse[1] = ipe.Matrix({10, 0, 0, 10, pos.x, pos.y})
71 local circ = ipe.Path(model.attributes, {ellipse})
72 p:insert(nil, circ, 0, p:layerOf(prim))
74 -- edit the circle instead of the mark itself
75 model:action_edit_path(#p, circ)
80 --------------------------------------------------------------------------------
81 -- pressing a key while editing the cycle --------------------------------------
83 -- saving old function
84 function _G.EDITTOOL:graph_backup_key(code, modifiers, text) end
85 _G.EDITTOOL.graph_backup_key = _G.EDITTOOL.key
88 function _G.EDITTOOL:key(code, modifiers, text)
89 -- The parameters of the key() function have changed in version
90 -- 7.1.7. Thus, we potentially have to remap the parameters.
94 self:graph_backup_key(code, modifiers, text)
95 if deactivateGraphMode then return end
97 -- react if and only if we are currently editing a mark and key ESC
98 -- or SPACE is pressed
99 -- if text ~= "\027" and code ~= 0x20 then return end
100 if text ~= "\027" and text ~= " " then return end
102 if not editing then return end
106 -- finding new and old position
107 local p = self.model:page()
109 local mark = p[currMarkId]
110 local oldPos = mark:matrix() * mark:position()
111 local newPos = circ:shape()[1][1]:translation()
113 -- remove the intermediate step of moving the cycle from the undo
114 -- stack and remove the cycle itself
115 local undoSt = self.model.undo
119 -- new action for the undo stack moving the mark and all endpoints
120 -- ending at the mark
121 local t = { label = "edit reference",
122 pno = self.model.pno,
123 vno = self.model.vno,
124 selection = self.model:selection(),
125 original = self.model:page():clone(),
127 undo = _G.revertOriginal,}
128 t.redo = function (t, doc)
129 p:transform(currMarkId, ipe.Translation(newPos-oldPos))
130 moveEndpoints(oldPos, newPos, p, self.model)
132 self.model:register(t)
135 -- function moving all endpoints and intermediate points in polylines
136 -- to newPos, if the squared distance to oldPos is at most sqEps
138 function moveEndpoints(oldPos, newPos, p, model)
140 for i, obj, sel, layer in p:objects() do
141 -- do nothing if the object is invisible and invisible objects
142 -- should not be moved
143 if not p:visible(model.vno, layer) and
144 not moveInvisibleObjects then
147 -- do nothing if it is not a path
148 if obj:type() ~= "path" then
151 local shape = obj:shape()
152 for _, subPath in ipairs(shape) do
153 if (subPath["type"] == "curve") then
154 for _,seg in ipairs(subPath) do
155 if (seg["type"] == "segment") then
156 for j, point in ipairs(seg) do
157 -- print(j, point, oldPos)
158 if (obj:matrix() * point - oldPos):sqLen() < sqEps then
159 seg[j] = obj:matrix():inverse() * newPos
160 -- print("test", seg[j])
163 elseif (seg["type"] == "spline") then
164 if (obj:matrix() * seg[1] - oldPos):sqLen() < sqEps then
165 seg[1] = obj:matrix():inverse() * newPos
167 if (obj:matrix() * seg[#seg] - oldPos):sqLen() < sqEps then
168 seg[#seg] = obj:matrix():inverse() * newPos
180 --------------------------------------------------------------------------------
181 -- working with groups ---------------------------------------------------------
183 local function regroup(elem)
185 for i, obj in ipairs(elem) do
186 if obj[1] ~= nil then
187 groupElem[#groupElem + 1] = regroup(obj)
189 groupElem[#groupElem + 1] = obj
192 return ipe.Group(groupElem)
195 local function ungroup(group)
196 local elem = group:elements()
198 for i, obj in ipairs(elem) do
199 if (obj:type() == "group") then
200 local subElem, subPlainElem = ungroup(obj)
202 for _, subObj in ipairs(subPlainElem) do
203 table.insert(plainElem, subObj)
206 table.insert(plainElem, obj)
209 return elem, plainElem
212 --------------------------------------------------------------------------------
213 -- shorten paths ---------------------------------------------------------------
215 function shortenObj(obj, lenSource, lenTarget)
216 if obj:type() == "path" then
217 local shape = obj:shape()
218 for _, subPath in ipairs(shape) do
219 local first = subPath[1]
220 local last = subPath[#subPath]
222 local p1 = obj:matrix() * first[1]
223 local p2 = obj:matrix() * first[2]
224 local pDelta = p2 - p1
225 local pNorm = pDelta:normalized()
226 local newP1 = p1 + pNorm*lenSource
228 local q1 = obj:matrix() * last[#last]
229 local q2 = obj:matrix() * last[#last - 1]
230 local qDelta = q2 - q1
231 local qNorm = qDelta:normalized()
232 local newQ1 = q1 + qNorm*lenTarget
234 first[1] = obj:matrix():inverse() * newP1
235 last[#last] = obj:matrix():inverse() * newQ1
241 function getString(model, string)
242 if ipeui.getString ~= nil then
243 return ipeui.getString(model.ui, "Enter length")
245 return model:getString("Enter length")
249 function shorten(model, num)
253 -- local str = ipeui.getString(model.ui, "Enter length")
254 -- local str = model:getString("Enter length")
255 local str = getString(model, "Enter length")
256 if not str or str:match("^%s*$)") then return end
257 if num == 1 then -- shorten target
258 lenTarget = tonumber(str)
259 elseif num == 2 then -- shorten source
260 lenSource = tonumber(str)
261 elseif num == 3 then -- shorten both
262 lenTarget = tonumber(str)
263 lenSource = tonumber(str)
266 -- start to edit the edges
267 local t = { label = "shorten edges",
270 selection = model:selection(),
271 original = model:page():clone(),
273 undo = _G.revertOriginal,}
274 t.redo = function (t, doc)
276 for _, i in ipairs(t.selection) do
280 for i, obj, sel, layer in p:objects() do
281 if sel and obj:type() == "group" then
282 local elem, plainElem = ungroup(obj)
283 for _,subobj in pairs(plainElem) do
284 shortenObj(subobj, lenSource, lenTarget)
286 p:replace(i, regroup(elem))
288 shortenObj(obj, lenSource, lenTarget)
297 { label = "toggle graph mode", run=toggleGraphMode },
298 { label = "toggle move invisible", run=toggleMoveInvisible },
299 { label = "shorten target", run=shorten },
300 { label = "shorten source", run=shorten },
301 { label = "shorten both", run=shorten },
304 ----------------------------------------------------------------------