1 ----------------------------------------------------------------------
3 ----------------------------------------------------------------------
6 about = [[ Some features making it easier to work with graphs. ]]
8 local deactivateGraphMode = false
10 function toggleGraphMode ()
11 if deactivateGraphMode then
12 deactivateGraphMode = false
14 deactivateGraphMode = true
19 local currMarkId = nil
21 --------------------------------------------------------------------------------
22 -- add an edit action for marks ------------------------------------------------
24 -- saving the old function
25 function _G.MODEL:graph_backup_actinon_edit () end
26 _G.MODEL.graph_backup_action_edit = _G.MODEL.action_edit
28 -- adding support for objects of type reference
29 function _G.MODEL:action_edit()
30 if deactivateGraphMode then
31 self:graph_backup_action_edit()
35 local prim = p:primarySelection()
37 self:graph_backup_action_edit()
41 if obj:type() == "reference" then
42 action_edit_reference (self, prim, obj)
44 self:graph_backup_action_edit()
49 -- starting to edit a mark
50 function action_edit_reference(model, prim, obj)
53 local p = model:page()
55 local pos = obj:matrix() * obj:position()
58 -- creating a circle at the position of the mark
59 local ellipse = {type="ellipse"}
60 ellipse[1] = ipe.Matrix({10, 0, 0, 10, pos.x, pos.y})
62 local circ = ipe.Path(model.attributes, {ellipse})
63 p:insert(nil, circ, 0, p:layerOf(prim))
65 -- edit the circle instead of the mark itself
66 model:action_edit_path(#p, circ)
71 --------------------------------------------------------------------------------
72 -- pressing a key while editing the cycle --------------------------------------
74 -- saving old function
75 function _G.EDITTOOL:graph_backup_key(code, modifiers, text) end
76 _G.EDITTOOL.graph_backup_key = _G.EDITTOOL.key
79 function _G.EDITTOOL:key(code, modifiers, text)
80 self:graph_backup_key(code, modifiers, text)
81 if deactivateGraphMode then return end
83 -- react if and only if we are currently editing a mark and key ESC
84 -- or SPACE is pressed
85 if text ~= "\027" and code ~= 0x20 then return end
86 if not editing then return end
90 -- finding new and old position
91 local p = self.model:page()
93 local mark = p[currMarkId]
94 local oldPos = mark:matrix() * mark:position()
95 local newPos = circ:shape()[1][1]:translation()
97 -- remove the intermediate step of moving the cycle from the undo
98 -- stack and remove the cycle itself
99 local undoSt = self.model.undo
103 -- new action for the undo stack moving the mark and all endpoints
104 -- ending at the mark
105 local t = { label = "edit reference",
106 pno = self.model.pno,
107 vno = self.model.vno,
108 selection = self.model:selection(),
109 original = self.model:page():clone(),
111 undo = _G.revertOriginal,}
112 t.redo = function (t, doc)
113 p:transform(currMarkId, ipe.Translation(newPos-oldPos))
114 moveEndpoints(oldPos, newPos, p)
116 self.model:register(t)
119 -- function moving all endpoints and intermediate points in polylines
120 -- to newPos, if the squared distance to oldPos is at most sqEps
122 function moveEndpoints(oldPos, newPos, p)
123 for i, obj, sel, layer in p:objects() do
124 if obj:type() == "path" then
125 local shape = obj:shape()
126 for _, subPath in ipairs(shape) do
127 if (subPath["type"] == "curve") then
128 for _,seg in ipairs(subPath) do
129 if (seg["type"] == "segment") then
130 for j, point in ipairs(seg) do
131 -- print(j, point, oldPos)
132 if (obj:matrix() * point - oldPos):sqLen() < sqEps then
133 seg[j] = obj:matrix():inverse() * newPos
134 -- print("test", seg[j])
137 elseif (seg["type"] == "spline") then
138 if (obj:matrix() * seg[1] - oldPos):sqLen() < sqEps then
139 seg[1] = obj:matrix():inverse() * newPos
141 if (obj:matrix() * seg[#seg] - oldPos):sqLen() < sqEps then
142 seg[#seg] = obj:matrix():inverse() * newPos
154 --------------------------------------------------------------------------------
155 -- working with groups ---------------------------------------------------------
157 local function regroup(elem)
159 for i, obj in ipairs(elem) do
160 if obj[1] ~= nil then
161 groupElem[#groupElem + 1] = regroup(obj)
163 groupElem[#groupElem + 1] = obj
166 return ipe.Group(groupElem)
169 local function ungroup(group)
170 local elem = group:elements()
172 for i, obj in ipairs(elem) do
173 if (obj:type() == "group") then
174 local subElem, subPlainElem = ungroup(obj)
176 for _, subObj in ipairs(subPlainElem) do
177 table.insert(plainElem, subObj)
180 table.insert(plainElem, obj)
183 return elem, plainElem
186 --------------------------------------------------------------------------------
187 -- shorten paths ---------------------------------------------------------------
189 function shortenObj(obj, lenSource, lenTarget)
190 if obj:type() == "path" then
191 local shape = obj:shape()
192 for _, subPath in ipairs(shape) do
193 local first = subPath[1]
194 local last = subPath[#subPath]
196 local p1 = obj:matrix() * first[1]
197 local p2 = obj:matrix() * first[2]
198 local pDelta = p2 - p1
199 local pNorm = pDelta:normalized()
200 local newP1 = p1 + pNorm*lenSource
202 local q1 = obj:matrix() * last[#last]
203 local q2 = obj:matrix() * last[#last - 1]
204 local qDelta = q2 - q1
205 local qNorm = qDelta:normalized()
206 local newQ1 = q1 + qNorm*lenTarget
208 first[1] = obj:matrix():inverse() * newP1
209 last[#last] = obj:matrix():inverse() * newQ1
215 function getString(model, string)
216 if ipeui.getString ~= nil then
217 return ipeui.getString(model.ui, "Enter length")
219 return model:getString("Enter length")
223 function shorten(model, num)
227 -- local str = ipeui.getString(model.ui, "Enter length")
228 -- local str = model:getString("Enter length")
229 local str = getString(model, "Enter length")
230 if not str or str:match("^%s*$)") then return end
231 if num == 1 then -- shorten target
232 lenTarget = tonumber(str)
233 elseif num == 2 then -- shorten source
234 lenSource = tonumber(str)
235 elseif num == 3 then -- shorten both
236 lenTarget = tonumber(str)
237 lenSource = tonumber(str)
240 -- start to edit the edges
241 local t = { label = "shorten edges",
244 selection = model:selection(),
245 original = model:page():clone(),
247 undo = _G.revertOriginal,}
248 t.redo = function (t, doc)
250 for _, i in ipairs(t.selection) do
254 for i, obj, sel, layer in p:objects() do
255 if sel and obj:type() == "group" then
256 local elem, plainElem = ungroup(obj)
257 for _,subobj in pairs(plainElem) do
258 shortenObj(subobj, lenSource, lenTarget)
260 p:replace(i, regroup(elem))
262 shortenObj(obj, lenSource, lenTarget)
271 { label = "toggle graph mode", run=toggleGraphMode },
272 { label = "shorten target", run=shorten },
273 { label = "shorten source", run=shorten },
274 { label = "shorten both", run=shorten },
277 ----------------------------------------------------------------------