389a26060d1cd1774212d2a8e445c1a9f20e1098
[Misc/ipe.git] / ipelets / decorator / decorator.lua
1
2 -- return a table of names associated with decorator symbols
3 function decorator_names(model)
4    local sheets = model.doc:sheets()
5    local symbols = sheets:allNames("symbol")
6    local res = {}
7    for _, name in pairs(symbols) do
8       if name:find("deco/") == 1 then
9          res[#res + 1] = name
10       end
11    end
12    return res
13 end
14
15 -- Changes the transformation matrix of a path object to the identitiy
16 -- matrix without canging the appearance of the object (i.e., the
17 -- transformation matrix is applied to the objects shape)
18 function cleanup_matrix(path_obj)
19    local matrix = path_obj:matrix()
20    local matrix_func = function (point) return matrix end
21    local shape = path_obj:shape()
22    transform_shape(shape, matrix_func)
23    path_obj:setShape(shape)
24    path_obj:setMatrix(ipe.Matrix())
25 end
26
27 -- Transform a shape by transforming every point using a matrix
28 -- returend by matrix_func.  The function matrix_func should take a
29 -- point and return a matrix (that is then used to transform this
30 -- point).
31 --
32 -- Arcs are also transformed.
33 function transform_shape(shape, matrix_func)
34    for _,path in pairs(shape) do
35       for _,subpath in ipairs(path) do
36          -- apply to every point
37          for i,point in ipairs(subpath) do
38             subpath[i] = matrix_func(point) * point
39          end
40
41          -- apply to arcs
42          if (subpath["type"] == "arc") then
43             local arc = subpath["arc"]
44             local center = arc:matrix():translation()
45             subpath["arc"] = matrix_func(center) * arc
46          end
47       end
48    end
49 end
50
51 -- Resizes a given shape such that the same transformation also
52 -- transforms bbox_source to bbox_target.  The transformation is done
53 -- by translating points of the shape such that all points in the same
54 -- quadrant with respect to center are translated in the same way.
55 --
56 -- Clearly, the center has to lie inside bbox_source.
57 function resize_shape(shape, center, bbox_source, bbox_target)
58    -- assert that the center lies inside bbox_source
59    assert(bbox_source:left() < center.x)
60    assert(center.x < bbox_source:left() + bbox_source:width())
61    assert(bbox_source:bottom() < center.y)
62    assert(center.y < bbox_source:bottom() + bbox_source:height())
63
64    -- translation of points to the left/right/top/bottom of the center
65    local dx_left = bbox_target:left() - bbox_source:left()
66    local dy_bottom = bbox_target:bottom() - bbox_source:bottom()
67    local dx_right = bbox_target:width() - bbox_source:width()
68    local dy_top = bbox_target:height() - bbox_source:height()
69
70    -- transformation
71    local matrix_func = function (point)
72       local dx = dx_left
73       local dy = dy_bottom
74       if (center.x < point.x) then dx = dx + dx_right end
75       if (center.y < point.y) then dy = dy + dy_top end
76       return ipe.Translation(dx, dy)
77    end
78    transform_shape(shape, matrix_func)
79 end
80
81 function bbox(obj, page)
82    local objno = #page + 1
83    page:insert(objno, obj, nil, page:layers()[1])
84    local bbox = page:bbox(objno)
85    page:remove(objno)
86    return bbox
87 end
88
89 function report_problem(model, text)
90    ipeui.messageBox(mainWindow(model), "warning", text, nil, nil)
91 end
92
93 function run_fancy_decorator (model)
94    local p = model:page()
95    local prim = p:primarySelection()
96    if (not prim) then
97       report_problem(model, "You must select somethings.")
98       return
99    end
100    local bbox_target = p:bbox(prim)
101
102    local deco_obj_group = ask_for_decorator(model)
103    if (not deco_obj_group) then return end   
104    if (deco_obj_group:type() ~= "group") then
105       report_problem(model, "The decoration must be a group.")
106       return
107    end
108
109    local objects = deco_obj_group:elements()
110    local last_obj = table.remove(objects, #objects)
111    local bbox_source = bbox(last_obj, p)
112    local center = ipe.Vector(bbox_source:left() + 0.5 * bbox_source:width(),
113                              bbox_source:bottom() + 0.5 * bbox_source:height())
114
115    if (#objects == 0) then
116       report_problem(model, "The decoration must be a group of at least two elements.")
117       return
118    end
119    for i,deco_obj in ipairs(objects) do
120       if (deco_obj:type() ~= "path") then
121          report_problem(model, "Each decoration object needs to be a path.")
122          return
123       end
124
125       cleanup_matrix(deco_obj)
126       local deco_shape = deco_obj:shape()
127       
128       resize_shape(deco_shape, center, bbox_source, bbox_target)
129
130       deco_obj:setShape(deco_shape)
131    end
132
133    local group = ipe.Group(objects)
134
135    model:creation("fancy decoration created", group)
136 end
137
138 -- Asks the user for a decorator and returns the chosen decorator
139 -- object or nil.
140 function ask_for_decorator(model)
141    local dialog = ipeui.Dialog(mainWindow(model), "Select a decorator.")
142    local decorators = decorator_names(model)
143    dialog:add("deco", "combo", decorators, 1, 1, 1, 2)
144    dialog:add("ok", "button", { label="&Ok", action="accept" }, 2, 2)
145    dialog:add("cancel", "button", { label="&Cancel", action="reject" }, 2, 1)
146    local r = dialog:execute()
147    if not r then return end
148    local deco_name = decorators[dialog:get("deco")]
149    local symbol = model.doc:sheets():find("symbol", deco_name)
150    return symbol:clone()
151 end
152
153 function mainWindow(model)
154    if model.ui.win == nil then
155       return model.ui
156    else
157       return model.ui:win()
158    end
159 end
160
161 label = "Decorator"
162 methods = {
163   { label = "Fancy decorator", run=run_fancy_decorator},
164 }