2 -- Helper for compatibility with different ipe-versions.
3 function mainWindow(model)
4 if model.ui.win == nil then
11 -- Changes the transformation matrix of a path object to the identitiy
12 -- matrix without canging the appearance of the object (i.e., the
13 -- transformation matrix is applied to the objects shape)
14 function cleanup_matrix(path_obj)
15 local matrix = path_obj:matrix()
16 local matrix_func = function (point) return matrix end
17 local shape = path_obj:shape()
18 transform_shape(shape, matrix_func)
19 path_obj:setShape(shape)
20 path_obj:setMatrix(ipe.Matrix())
23 -- Transform a shape by transforming every point using a matrix
24 -- returend by matrix_func. The function matrix_func should take a
25 -- point and return a matrix (that is then used to transform this
28 -- Arcs are also transformed.
29 function transform_shape(shape, matrix_func)
30 for _,path in pairs(shape) do
31 for _,subpath in ipairs(path) do
32 -- apply to every point
33 for i,point in ipairs(subpath) do
34 subpath[i] = matrix_func(point) * point
38 if (subpath["type"] == "arc") then
39 local arc = subpath["arc"]
40 local center = arc:matrix():translation()
41 subpath["arc"] = matrix_func(center) * arc
47 -- Resizes a given shape such that the same transformation also
48 -- transforms bbox_source to bbox_target. The transformation is done
49 -- by translating points of the shape such that all points in the same
50 -- quadrant with respect to center are translated in the same way.
52 -- Clearly, the center has to lie inside bbox_source.
53 function resize_shape(shape, center, bbox_source, bbox_target)
54 -- assert that the center lies inside bbox_source
55 assert(bbox_source:left() < center.x)
56 assert(center.x < bbox_source:left() + bbox_source:width())
57 assert(bbox_source:bottom() < center.y)
58 assert(center.y < bbox_source:bottom() + bbox_source:height())
60 -- translation of points to the left/right/top/bottom of the center
61 local dx_left = bbox_target:left() - bbox_source:left()
62 local dy_bottom = bbox_target:bottom() - bbox_source:bottom()
63 local dx_right = bbox_target:width() - bbox_source:width()
64 local dy_top = bbox_target:height() - bbox_source:height()
67 local matrix_func = function (point)
70 if (center.x < point.x) then dx = dx + dx_right end
71 if (center.y < point.y) then dy = dy + dy_top end
72 return ipe.Translation(dx, dy)
74 transform_shape(shape, matrix_func)
77 -- Bounding box of a given object that is not currently contained in
78 -- the page. If the object is already part of the page, simply use
80 function bbox(obj, page)
81 local objno = #page + 1
82 page:insert(objno, obj, nil, page:layers()[1])
83 local bbox = page:bbox(objno)
88 -- The bounding box of all selected objects.
89 function bbox_of_selected_objects(page)
90 local bbox = page:bbox(page:primarySelection())
91 for obj = 1, #page, 1 do
92 if page:select(obj) then
93 bbox:add(page:bbox(obj))
99 -- Show a warning to the user.
100 function report_problem(model, text)
101 ipeui.messageBox(mainWindow(model), "warning", text, nil, nil)
104 -- Return a table of names associated with decorator symbols.
105 function decorator_names(model)
106 local sheets = model.doc:sheets()
107 local symbols = sheets:allNames("symbol")
109 for _, name in pairs(symbols) do
110 if name:find("deco/") == 1 then
117 -- Check whether a given deco object is usable.
118 function check_decorator_object(model, deco_obj_group)
119 if (deco_obj_group:type() ~= "group") then
120 report_problem(model, "The decoration must be a group.")
124 local objects = deco_obj_group:elements()
125 local last_obj = table.remove(objects, #objects)
126 if (#objects == 0) then
127 report_problem(model, "The decoration must be a group of at least two elements (the topmost element is a placeholder for the objects that are decorated, all other elements are the decoration).")
131 for i,deco_obj in ipairs(objects) do
132 if (deco_obj:type() ~= "path") then
133 report_problem(model, "Each decoration object needs to be a path.")
140 -- Ask the user for a decorator and run the decoration.
141 function run_decorator (model)
142 local p = model:page()
143 local prim = p:primarySelection()
145 report_problem(model, "You must select somethings.")
148 local bbox_target = bbox_of_selected_objects(p)
150 local deco_obj_group = ask_for_decorator(model)
151 if (not deco_obj_group) then return end
152 if (not check_decorator_object(model, deco_obj_group)) then return end
154 local objects = deco_obj_group:elements()
155 local last_obj = table.remove(objects, #objects)
156 local bbox_source = bbox(last_obj, p)
157 local center = ipe.Vector(bbox_source:left() + 0.5 * bbox_source:width(),
158 bbox_source:bottom() + 0.5 * bbox_source:height())
160 for i,deco_obj in ipairs(objects) do
161 cleanup_matrix(deco_obj)
162 local deco_shape = deco_obj:shape()
164 resize_shape(deco_shape, center, bbox_source, bbox_target)
166 deco_obj:setShape(deco_shape)
169 local group = ipe.Group(objects)
171 model:creation("decoration created", group)
174 -- Asks the user for a decorator and returns the chosen decorator
176 function ask_for_decorator(model)
177 local dialog = ipeui.Dialog(mainWindow(model), "Select a decorator.")
178 local decorators = decorator_names(model)
179 dialog:add("deco", "combo", decorators, 1, 1, 1, 2)
180 dialog:add("ok", "button", { label="&Ok", action="accept" }, 2, 2)
181 dialog:add("cancel", "button", { label="&Cancel", action="reject" }, 2, 1)
182 local r = dialog:execute()
183 if not r then return end
184 local deco_name = decorators[dialog:get("deco")]
185 local symbol = model.doc:sheets():find("symbol", deco_name)
186 return symbol:clone()
189 -- Basically create symbol taken from "symbols.lua" with two minor
190 -- changes. First, the symbol is created only if the call to
191 -- check_decorator_object is successful. Second, the prefix "deco/"
192 -- is added to the symbols name.
193 function create_deco_obj(model, num)
194 local p = model:page()
195 local prim = p:primarySelection()
196 if not prim then model.ui:explain("no selection") return end
197 if not check_decorator_object(model, p[prim]) then
198 model.ui:explain("no selection")
201 local str = model:getString("Enter name of new symbol")
202 if not str or str:match("^%s*$") then return end
203 local name = "deco/" .. str:match("^%s*%S+%s*$")
204 local old = model.doc:sheets():find("symbol", name)
206 local r = ipeui.messageBox(mainWindow(model), "question",
207 "Symbol '" .. name .. "' already exists",
208 "Do you want to proceed?",
210 if r <= 0 then return end
213 if num == 2 then -- new stylesheet
214 local sheet = ipe.Sheet()
215 sheet:add("symbol", name, p[prim])
216 local t = { label = methods[num].label,
219 t.redo = function (t, doc)
220 doc:sheets():insert(1, t.sheet:clone())
222 t.undo = function (t, doc)
223 doc:sheets():remove(1)
226 else -- top stylesheet
227 local sheet = model.doc:sheets():sheet(1)
228 local t = { label = methods[num].label,
229 original = sheet:clone(),
230 final = sheet:clone(),
232 t.final:add("symbol", name, p[prim])
233 t.redo = function (t, doc)
234 doc:sheets():remove(1)
235 doc:sheets():insert(1, t.final:clone())
237 t.undo = function (t, doc)
238 doc:sheets():remove(1)
239 doc:sheets():insert(1, t.original:clone())
247 { label = "decorate", run=run_decorator },
248 { label = "create deco-object (in new style sheet)", run=create_deco_obj },
249 { label = "create deco-object (in top style sheet)", run=create_deco_obj },