function for creating decorator objects
[Misc/ipe.git] / ipelets / decorator / decorator.lua
index cffa3b515ba464815844960b450c8a6aeef96bfc..2c25d248444575d6f16ab786c0b6f457b6a3c27d 100644 (file)
@@ -1,15 +1,11 @@
 
--- return a table of names associated with decorator symbols
-function decorator_names(model)
-   local sheets = model.doc:sheets()
-   local symbols = sheets:allNames("symbol")
-   local res = {}
-   for _, name in pairs(symbols) do
-      if name:find("deco/") == 1 then
-        res[#res + 1] = name
-      end
+-- Helper for compatibility with different ipe-versions.
+function mainWindow(model)
+   if model.ui.win == nil then
+      return model.ui
+   else
+      return model.ui:win()
    end
-   return res
 end
 
 -- Changes the transformation matrix of a path object to the identitiy
@@ -78,6 +74,9 @@ function resize_shape(shape, center, bbox_source, bbox_target)
    transform_shape(shape, matrix_func)
 end
 
+-- Bounding box of a given object that is not currently contained in
+-- the page.  If the object is already part of the page, simply use
+-- p:bbox(obj).
 function bbox(obj, page)
    local objno = #page + 1
    page:insert(objno, obj, nil, page:layers()[1])
@@ -86,33 +85,79 @@ function bbox(obj, page)
    return bbox
 end
 
+-- The bounding box of all selected objects.
+function bbox_of_selected_objects(page)
+   local bbox = page:bbox(page:primarySelection())
+   for obj = 1, #page, 1 do
+      if page:select(obj) then
+        bbox:add(page:bbox(obj))
+      end
+   end
+   return bbox
+end
+
+-- Show a warning to the user.
 function report_problem(model, text)
    ipeui.messageBox(mainWindow(model), "warning", text, nil, nil)
 end
 
-function run_fancy_decorator (model)
-   local p = model:page()
-   local prim = p:primarySelection()
-   local bbox_target = p:bbox(prim)
+-- Return a table of names associated with decorator symbols.
+function decorator_names(model)
+   local sheets = model.doc:sheets()
+   local symbols = sheets:allNames("symbol")
+   local res = {}
+   for _, name in pairs(symbols) do
+      if name:find("deco/") == 1 then
+        res[#res + 1] = name
+      end
+   end
+   return res
+end
 
-   local deco_obj_group = ask_for_decorator(model)
+-- Check whether a given deco object is usable.
+function check_decorator_object(model, deco_obj_group)
    if (deco_obj_group:type() ~= "group") then
       report_problem(model, "The decoration must be a group.")
-      return
+      return false
    end
 
    local objects = deco_obj_group:elements()
    local last_obj = table.remove(objects, #objects)
-   local bbox_source = bbox(last_obj, p)
-   local center = ipe.Vector(bbox_source:left() + 0.5 * bbox_source:width(),
-                            bbox_source:bottom() + 0.5 * bbox_source:height())
-   
+   if (#objects == 0) then
+      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).")
+      return false
+   end
+
    for i,deco_obj in ipairs(objects) do
       if (deco_obj:type() ~= "path") then
         report_problem(model, "Each decoration object needs to be a path.")
-        return
+        return false
       end
+   end
+   return true
+end
+
+-- Ask the user for a decorator and run the decoration.
+function run_decorator (model)
+   local p = model:page()
+   local prim = p:primarySelection()
+   if (not prim) then
+      report_problem(model, "You must select somethings.")
+      return
+   end
+   local bbox_target = bbox_of_selected_objects(p)
 
+   local deco_obj_group = ask_for_decorator(model)
+   if (not deco_obj_group) then return end
+   if (not check_decorator_object(model, deco_obj_group)) then return end
+
+   local objects = deco_obj_group:elements()
+   local last_obj = table.remove(objects, #objects)
+   local bbox_source = bbox(last_obj, p)
+   local center = ipe.Vector(bbox_source:left() + 0.5 * bbox_source:width(),
+                            bbox_source:bottom() + 0.5 * bbox_source:height())
+
+   for i,deco_obj in ipairs(objects) do
       cleanup_matrix(deco_obj)
       local deco_shape = deco_obj:shape()
       
@@ -123,7 +168,7 @@ function run_fancy_decorator (model)
 
    local group = ipe.Group(objects)
 
-   model:creation("fancy decoration created", group)
+   model:creation("decoration created", group)
 end
 
 -- Asks the user for a decorator and returns the chosen decorator
@@ -141,89 +186,65 @@ function ask_for_decorator(model)
    return symbol:clone()
 end
 
--- Decorate something given by its bounding box with a given deco
--- object, which needs to be a path.
-function decorate(model, bbox, deco)
-   if (deco:type() ~= "path") then
-      report_problem(model, "The decoration needs to be a path.")
-      return
-   end
-
-   local shape = deco:shape()
-   local m = deco:matrix()
-   for _,path in pairs(shape) do
-      for _,subpath in ipairs(path) do  
-        -- move all points
-        for i,point in ipairs(subpath) do
-           subpath[i] = translation(bbox, m*point) * m*point
-        end
-
-        -- for acs, the center must be translated separately
-        if (subpath["type"] == "arc") then
-           local arc = subpath["arc"]
-           local arc_pos = arc:matrix():translation()
-           subpath["arc"] = translation(bbox, m*arc_pos) * m * arc
-        end
-      end
-   end
-   -- update model
-   deco:setShape(shape)
-   deco:setMatrix(ipe.Matrix())
-   model:creation("create", deco)
-end
-
--- The translation matrix that should be applied to a given point when
--- doing the decoration.
-function translation(bbox, point)
-   local dx = 0
-   local dy = 0
-   if (point.x > 0) then
-      dx = dx + bbox:width()
-   end
-   if (point.y > 0) then
-      dy = dy + bbox:height()
-   end
-   dx = dx + bbox:left()
-   dy = dy + bbox:bottom()
-   return ipe.Translation(dx, dy)
-end
-
-function mainWindow(model)
-   if model.ui.win == nil then
-      return model.ui
-   else
-      return model.ui:win()
-   end
-end
-
-function run_decorator(model)
-   -- get bbox of primary selection
+-- Basically create symbol taken from "symbols.lua" with two minor
+-- changes.  First, the symbol is created only if the call to
+-- check_decorator_object is successful.  Second, the prefix "deco/"
+-- is added to the symbols name.
+function create_deco_obj(model, num)
    local p = model:page()
    local prim = p:primarySelection()
-   if not prim then
-      model.ui:explain("An object must be selected.")
+   if not prim then model.ui:explain("no selection") return end
+   if not check_decorator_object(model, p[prim]) then
+      model.ui:explain("no selection")
       return
    end
-   local bbox = p:bbox(prim)
-
-   -- create decorator object
-   local dialog = ipeui.Dialog(mainWindow(model), "Select a decorator.")
-   local decorators = decorator_names(model)
-   dialog:add("deco", "combo", decorators, 1, 1, 1, 2)
-   dialog:add("ok", "button", { label="&Ok", action="accept" }, 2, 2)
-   dialog:add("cancel", "button", { label="&Cancel", action="reject" }, 2, 1)
-   local r = dialog:execute()
-   if not r then return end
-   local deco_name = decorators[dialog:get("deco")]
-   local symbol = model.doc:sheets():find("symbol", deco_name)
-   local deco = symbol:clone()
+   local str = model:getString("Enter name of new symbol")
+   if not str or str:match("^%s*$") then return end
+   local name = "deco/" .. str:match("^%s*%S+%s*$")
+   local old = model.doc:sheets():find("symbol", name)
+   if old then
+      local r = ipeui.messageBox(mainWindow(model), "question",
+                                "Symbol '" .. name .. "' already exists",
+                                "Do you want to proceed?",
+                                "okcancel")
+      if r <= 0 then return end
+   end
 
-   -- run the decoration
-   decorate(model, bbox, deco)
+   if num == 2 then -- new stylesheet
+      local sheet = ipe.Sheet()
+      sheet:add("symbol", name, p[prim])
+      local t = { label = methods[num].label,
+                 sheet = sheet,
+      }
+      t.redo = function (t, doc)
+        doc:sheets():insert(1, t.sheet:clone())
+      end
+      t.undo = function (t, doc)
+        doc:sheets():remove(1)
+      end
+      model:register(t)
+   else  -- top stylesheet
+      local sheet = model.doc:sheets():sheet(1)
+      local t = { label = methods[num].label,
+                 original = sheet:clone(),
+                 final = sheet:clone(),
+      }
+      t.final:add("symbol", name, p[prim])
+      t.redo = function (t, doc)
+        doc:sheets():remove(1)
+        doc:sheets():insert(1, t.final:clone())
+      end
+      t.undo = function (t, doc)
+        doc:sheets():remove(1)
+        doc:sheets():insert(1, t.original:clone())
+      end
+      model:register(t)
+   end
 end
 
 label = "Decorator"
 methods = {
-  { label = "Decorate", run=run_decorator},
-  { label = "Fancy decorator", run=run_fancy_decorator},
+  { label = "decorate", run=run_decorator },
+  { label = "create deco-object (in new style sheet)", run=create_deco_obj },
+  { label = "create deco-object (in top style sheet)", run=create_deco_obj },
 }