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 -- Ask the user for a decorator and run the decoration.
118 function run_decorator (model)
119 local p = model:page()
120 local prim = p:primarySelection()
122 report_problem(model, "You must select somethings.")
125 -- local bbox_target = p:bbox(prim)
126 local bbox_target = bbox_of_selected_objects(p)
128 local deco_obj_group = ask_for_decorator(model)
129 if (not deco_obj_group) then return end
130 if (deco_obj_group:type() ~= "group") then
131 report_problem(model, "The decoration must be a group.")
135 local objects = deco_obj_group:elements()
136 local last_obj = table.remove(objects, #objects)
137 local bbox_source = bbox(last_obj, p)
138 local center = ipe.Vector(bbox_source:left() + 0.5 * bbox_source:width(),
139 bbox_source:bottom() + 0.5 * bbox_source:height())
141 if (#objects == 0) then
142 report_problem(model, "The decoration must be a group of at least two elements.")
145 for i,deco_obj in ipairs(objects) do
146 if (deco_obj:type() ~= "path") then
147 report_problem(model, "Each decoration object needs to be a path.")
151 cleanup_matrix(deco_obj)
152 local deco_shape = deco_obj:shape()
154 resize_shape(deco_shape, center, bbox_source, bbox_target)
156 deco_obj:setShape(deco_shape)
159 local group = ipe.Group(objects)
161 model:creation("decoration created", group)
164 -- Asks the user for a decorator and returns the chosen decorator
166 function ask_for_decorator(model)
167 local dialog = ipeui.Dialog(mainWindow(model), "Select a decorator.")
168 local decorators = decorator_names(model)
169 dialog:add("deco", "combo", decorators, 1, 1, 1, 2)
170 dialog:add("ok", "button", { label="&Ok", action="accept" }, 2, 2)
171 dialog:add("cancel", "button", { label="&Cancel", action="reject" }, 2, 1)
172 local r = dialog:execute()
173 if not r then return end
174 local deco_name = decorators[dialog:get("deco")]
175 local symbol = model.doc:sheets():find("symbol", deco_name)
176 return symbol:clone()
181 { label = "decorate", run=run_decorator},