diff --git a/extensions/fablabchemnitz/gradient_saver/GUI.glade b/extensions/fablabchemnitz/gradient_saver/GUI.glade
new file mode 100644
index 00000000..e8cf6c39
--- /dev/null
+++ b/extensions/fablabchemnitz/gradient_saver/GUI.glade
@@ -0,0 +1,667 @@
+
+
+
+
+
+
+ False
+ Information
+ dialog-information
+ dialog
+ window
+
+
+
+
+
+ False
+ 10
+ 10
+ 10
+ 10
+ vertical
+ 2
+
+
+ False
+ 10
+ 10
+ center
+
+
+ Close
+ True
+ True
+ True
+
+
+
+ True
+ True
+ 0
+
+
+
+
+ False
+ False
+ 0
+
+
+
+
+ True
+ False
+
+
+ 55
+ True
+ False
+ gtk-info
+ 6
+
+
+ False
+ True
+ 0
+
+
+
+
+ True
+ False
+ label
+
+
+ False
+ True
+ 1
+
+
+
+
+ False
+ True
+ 1
+
+
+
+
+
+
diff --git a/extensions/fablabchemnitz/gradient_saver/gradient_saver.inx b/extensions/fablabchemnitz/gradient_saver/gradient_saver.inx
new file mode 100644
index 00000000..0a68851d
--- /dev/null
+++ b/extensions/fablabchemnitz/gradient_saver/gradient_saver.inx
@@ -0,0 +1,16 @@
+
+
+ Gradient Saver
+ fablabchemnitz.de.gradient_saver
+
+ all
+
+
+
+
+
+
+
+
diff --git a/extensions/fablabchemnitz/gradient_saver/gradient_saver.py b/extensions/fablabchemnitz/gradient_saver/gradient_saver.py
new file mode 100644
index 00000000..d9deb528
--- /dev/null
+++ b/extensions/fablabchemnitz/gradient_saver/gradient_saver.py
@@ -0,0 +1,385 @@
+#! /usr/bin/env python3
+
+# OS modules
+import os
+import sys
+
+import gi
+gi.require_version("Gtk", "3.0")
+from gi.repository import Gtk
+
+# inkscape extension files
+import cairo
+from lxml import etree
+import inkex # required
+from inkex.utils import NSS
+from inkex.utils import errormsg as show_errormsg
+from inkex.styles import Style
+from inkex.colors import Color
+
+__version__ = '1.0.0'
+
+saved_gradient_path = "../my-gradients.svg"
+
+def create_new_file(gradient_data):
+ root = etree.Element("svg", nsmap=NSS)
+ def_tree = etree.SubElement(root, "defs")
+ for i, item in enumerate(gradient_data):
+ gradient = etree.SubElement(def_tree, item.tag, attrib=item.attrib)
+ for j, gradient_stop in enumerate(item):
+ etree.SubElement(gradient, gradient_stop.tag, attrib=gradient_stop.attrib, id="stop%d%d" % (i, j))
+ with open(saved_gradient_path, "w") as f:
+ f.write(etree.tostring(
+ root, encoding="utf-8", xml_declaration=True, pretty_print=True).decode("utf-8"))
+
+def save_to_file(data):
+ """ Wrapper for saving gradients to file. """
+ if len(data) == 0:
+ return 1
+ else:
+ try:
+ # read previous data then append it with current data
+ if os.path.exists(saved_gradient_path):
+ previous_data = load_gradients_from_file()
+ data = previous_data + data
+ create_new_file(data)
+ return 0
+ except Exception as e:
+ import traceback
+ show_errormsg(e)
+ show_errormsg(traceback.print_exc())
+ return -1
+
+def load_gradients_from_file():
+ """ Load gradients from saved gradient, returned as List """
+ if os.path.exists(saved_gradient_path) and os.stat(saved_gradient_path).st_size != 0:
+ root = etree.parse(saved_gradient_path)
+ mygradients = root.xpath("//linearGradient", namespaces=NSS)
+ else:
+ mygradients = []
+ return mygradients
+
+def read_stop_gradient(gradient):
+ stop_data = {
+ "id": gradient.attrib.get("id"),
+ "stops": []
+ }
+ for stop in gradient:
+ offset = stop.attrib.get("offset")
+ style = Style(Style.parse_str(stop.attrib['style']))
+ color = Color.parse_str(style.get("stop-color"))[1]
+ opacity = style.get("stop-opacity")
+ stop_data.get("stops").append(
+ tuple([float(offset)] + [x/256.0 for x in color] + [float(opacity)]))
+ return stop_data
+
+class MainWindow(Gtk.Builder):
+ def __init__(self, gradientSaver):
+ Gtk.Builder.__init__(self)
+ self.gradientSaver = gradientSaver
+ self.add_from_file("GUI.glade")
+ self.window = self.get_object("window")
+ self.app_version = self.get_object("extension_version")
+ self.app_version.set_label(self.app_version.get_label().replace("$VERSION",__version__))
+ self.information_dialog = self.get_object("information_dialog")
+ # parsing components
+ # save gradient components
+ self.save_container = self.get_object("save_gradients_container")
+ save_template = self.get_object("save_gradient1")
+ self.save_container.remove(save_template)
+ for idx, item in enumerate(self.gradientSaver.doc_selected_gradients):
+ new_save_template = self.SaveGradientTemplate(item)
+ new_save_template.set_name("gradient%d" % idx)
+ self.save_container.add(new_save_template)
+ self.save_container.show_all()
+ # - end save gradient components
+ # load gradient components
+ self.load_container = self.get_object("load_gradients_container")
+ load_template = self.get_object("load_gradient1")
+ self.load_container.remove(load_template)
+ # - end load gradient components
+ # show the GUI
+ self.connect_signals(self.Handler(self))
+ self.window.show()
+
+ class SaveGradientTemplate(Gtk.HBox):
+ """
+ Template for generating gradient name
+ and preview of selected object in the save page.
+ """
+
+ def __init__(self, gradient_data):
+ Gtk.HBox.__init__(self)
+ self.gradient_data = gradient_data
+ self.set_spacing(20)
+ preview = Gtk.DrawingArea()
+ preview.set_size_request(150, 42)
+ preview.set_app_paintable(True)
+ preview.connect("draw", self.on_draw, gradient_data)
+ self.pack_start(preview, False, True, 0)
+ self.input_entry = Gtk.Entry()
+ self.input_entry.set_placeholder_text("e.g Beautiful Color")
+ self.input_entry.set_size_request(250, 42)
+ self.input_entry.set_text(gradient_data.get("id"))
+ self.input_entry.set_max_length(25)
+ self.pack_start(self.input_entry, False, True, 1)
+
+ def on_draw(self, wid, cr, data):
+ """
+ Calllback for draw signal for rendering gradient.
+ params:
+ - wid :GtkWidget
+ - cr :Cairo
+ - data :list -> gradient data
+ """
+ lg = cairo.LinearGradient(0.0, 20.0, 150.0, 20.0)
+ for stop in data["stops"]:
+ lg.add_color_stop_rgba(
+ stop[0], stop[1], stop[2], stop[3], stop[4])
+ cr.rectangle(10.0, 0.0, 150.0, 42.0)
+ cr.set_source(lg)
+ cr.fill()
+
+ def get_save_gradient_text(self):
+ return self.input_entry.get_text()
+
+ def get_compiled_gradient(self, new_id):
+ # compiling gradient stops
+ root = etree.Element("linearGradient", id=new_id)
+ for idx, stop in enumerate(self.gradient_data["stops"]):
+ stop_id = self.get_name() + str(idx)
+ offset = stop[0]
+ color = Color([stop[1], stop[2], stop[3]],"rgb")
+ opacity = stop[4]
+ tmp_stops = {
+ "id": stop_id,
+ "offset": str(offset),
+ "style": Style({
+ "stop-color": str(color),
+ "stop-opacity": str(opacity)
+ }).to_str()
+ }
+ current_stop = etree.SubElement(root, "stop", attrib=tmp_stops)
+ return root
+
+ class LoadGradientTemplate(Gtk.FlowBoxChild):
+ """
+ Template for generating gradient name
+ and preview of saved gradient in the load page.
+ """
+ def __init__(self, gradient_data):
+ Gtk.FlowBoxChild.__init__(self)
+ self.gradient_data = gradient_data
+ self.set_size_request(60,32)
+ self.set_halign(Gtk.Align.START)
+ self.set_valign(Gtk.Align.START)
+ container = Gtk.HBox()
+ container.set_spacing(5)
+ container.set_baseline_position(Gtk.BaselinePosition.TOP)
+ self.checkbox = Gtk.CheckButton()
+ self.checkbox.draw_indicator = True
+ container.pack_start(self.checkbox,False,True,0)
+ preview = Gtk.DrawingArea()
+ preview.set_size_request(100, 32)
+ preview.set_app_paintable(True)
+ preview.connect("draw", self.on_draw, gradient_data)
+ container.pack_start(preview,False,True,0)
+ self.text_gradient = Gtk.Label()
+ self.text_gradient.set_text(gradient_data.get("id"))
+ self.text_gradient.set_line_wrap(True)
+ self.text_gradient.set_line_wrap_mode(1)
+ self.text_gradient.set_max_width_chars(25)
+ container.pack_start(self.text_gradient,False,True,0)
+ self.add(container)
+
+ def on_draw(self, wid, cr, data):
+ """
+ Calllback for draw signal for rendering gradient.
+ params:
+ - wid :GtkWidget
+ - cr :Cairo
+ - data :list -> gradient data
+ """
+ lg = cairo.LinearGradient(0.0, 20.0, 100.0, 20.0)
+ for stop in data["stops"]:
+ lg.add_color_stop_rgba(
+ stop[0], stop[1], stop[2], stop[3], stop[4])
+ cr.rectangle(10.0, 0.0, 110.0, 32.0)
+ cr.set_source(lg)
+ cr.fill()
+
+ class Handler:
+ """ Signal Handler for GUI """
+
+ def __init__(self, main_window):
+ self.main_window = main_window
+
+ def onDestroy(self, *args):
+ Gtk.main_quit()
+
+ def onSwitchPage(self, notebook, page, page_num):
+ if page_num == 0: # save tab
+ pass
+ elif page_num == 1: # load/remove tab
+ self.main_window.gradients_to_load = []
+ for children in self.main_window.load_container.get_children():
+ self.main_window.load_container.remove(children)
+ loaded_gradients = load_gradients_from_file()
+ # TODO render with disabled checkbox if it already exists in current project doc
+ for idx,gradient in enumerate(loaded_gradients):
+ # parse gradient stops
+ stop_data = read_stop_gradient(gradient)
+ gradient_info = self.main_window.LoadGradientTemplate(stop_data)
+ gradient_info.checkbox.connect("toggled",self.onLoadGradientToggled, gradient)
+ gradient_info.set_name("gradient%d" % idx)
+ self.main_window.load_container.add(gradient_info)
+ self.main_window.load_container.show_all()
+ else:
+ pass
+
+ def onSaveGradientClicked(self, button):
+ text = ""
+ gradient_to_save = []
+ # get all gradient data in save_container
+ for item in self.main_window.save_container.get_children():
+ # get new gradient name
+ new_name_gradient = item.get_save_gradient_text()
+ # strip all special chars
+ if not new_name_gradient.isalnum():
+ new_name_gradient = ''.join(e for e in new_name_gradient if e.isalnum())
+ # get gradient data
+ gradient_data = item.get_compiled_gradient(new_name_gradient)
+ text += "{0}\n-----\n".format(etree.tostring(gradient_data))
+ gradient_to_save.append(gradient_data)
+ # save to file
+ status = save_to_file(gradient_to_save)
+ if status == 0:
+ info = "%d gradients saved successfully!" % len(gradient_to_save)
+ # reload current document info with saved gradients
+ self.main_window.gradientSaver.reload_current_gradients(gradient_to_save)
+ elif status == 1:
+ info = "Nothing to save, there is no object with gradient selected. Exiting..."
+ elif status == -1:
+ info = "Internal Error (-1)! "
+ # showing popup information
+ self.main_window.get_object("information_text").set_text(info)
+ self.main_window.information_dialog.set_title("Save Gradient Information")
+ self.main_window.information_dialog.show_all()
+
+ def onLoadGradientToggled(self, togglebutton, gradient):
+ # if active, queue gradient, otherwise pop it
+ if togglebutton.get_active():
+ self.main_window.gradients_to_load.append(gradient)
+ else:
+ self.main_window.gradients_to_load.remove(gradient)
+
+ def onLoadGradientClicked(self, button):
+ if len(self.main_window.gradients_to_load) > 0:
+ self.main_window.gradientSaver.insert_new_gradients_to_current_doc(self.main_window.gradients_to_load)
+ teks = "Successfully loading these gradients:\n"
+ teks += "".join(["- "+gradient.attrib["id"]+"\n" for gradient in self.main_window.gradients_to_load])
+ else:
+ teks = "No gradient(s) selected to load. Exiting..."
+ self.main_window.get_object("information_text").set_text(teks)
+ self.main_window.information_dialog.set_title("Load Gradient Information")
+ self.main_window.information_dialog.show_all()
+
+ def onRemoveGradientClicked(self, button):
+ loaded_gradients = load_gradients_from_file()
+ if len(self.main_window.gradients_to_load) > 0:
+ gradient_to_remove = [gradient.attrib["id"] for gradient in self.main_window.gradients_to_load]
+ new_gradient_after = [gradient for gradient in loaded_gradients if gradient.attrib["id"] not in gradient_to_remove]
+ create_new_file(new_gradient_after)
+ teks = "Successfully removing these gradients:\n"
+ teks += "".join(["- "+gradient+"\n" for gradient in gradient_to_remove])
+ else:
+ teks = "No gradient(s) selected to load. Exiting..."
+ self.main_window.get_object("information_text").set_text(teks)
+ self.main_window.information_dialog.set_title("Remove Gradient Information")
+ self.main_window.information_dialog.show_all()
+
+
+class GradientSaver(inkex.Effect):
+ def __init__(self):
+ " define how the options are mapped from the inx file "
+ inkex.Effect.__init__(self) # initialize the super class
+ try:
+ self.tty = open("/dev/tty", 'w')
+ except:
+ self.tty = open(os.devnull, 'w')
+ self.doc_selected_gradients = []
+
+ def insert_new_gradients_to_current_doc(self, gradients):
+ defs_node = self.svg.getElement("//svg:defs")
+ for item in gradients:
+ gradient = etree.SubElement(defs_node,item.tag,attrib=item.attrib)
+ for stop in item:
+ etree.SubElement(gradient,stop.tag,attrib=stop.attrib)
+
+ def reload_current_gradients(self, new_data):
+ " reload gradients information in current project with stored gradient "
+ for idx,gradient in enumerate(self.doc_selected_gradients):
+ # set old gradient id to new id
+ real_node = self.svg.getElement("//*[@id='%s']" % gradient["id"])
+ # remove inkscape collect first
+ real_node.attrib.pop("{"+NSS["inkscape"]+"}collect", None)
+ real_node.attrib["id"] = new_data[idx].attrib["id"]
+ # set old xlink:href to new id
+ node_href = self.svg.getElement("//*[@xlink:href='#%s']" % gradient["id"])
+ node_href.attrib["{"+NSS["xlink"]+"}href"] = "#"+new_data[idx].attrib["id"]
+ # last set up inkscape collect again
+ real_node.attrib["{"+NSS["inkscape"]+"}collect"] = "always"
+
+ def get_all_doc_gradients(self):
+ """TODO
+ retrieve all gradient sources of current project document
+ """
+ pass
+
+ def get_selected_gradients_data(self):
+ selected_objects = self.svg.selected
+ gradient_list = []
+ if len(selected_objects) > 0:
+ for item in selected_objects:
+ style = Style(Style.parse_str(selected_objects.get(item).get('style')))
+ fill = stroke = "None"
+ if style.get("fill"):
+ fill = style.get("fill")[5:-1] if "url" in style.get("fill") else "None"
+ if style.get("stroke"):
+ stroke = style("stroke")[5:-1] if "url" in style.get("stroke") else "None"
+ if fill == "None" and stroke == "None":
+ continue
+ # read fill data
+ if "radialGradient" in fill or "linearGradient" in fill:
+ real_fill = self.svg.getElementById(fill).attrib["{"+NSS["xlink"]+"}href"][1:]
+ real_fill_node = self.svg.getElementById(real_fill)
+ if real_fill_node not in gradient_list:
+ gradient_list.append(real_fill_node)
+ # read stroke data
+ if "radialGradient" in stroke or "linearGradient" in stroke:
+ real_stroke = self.svg.getElementById(stroke).attrib["{"+NSS["xlink"]+"}href"][1:]
+ real_stroke_node = self.svg.getElementById(real_stroke)
+ if real_stroke_node not in gradient_list:
+ gradient_list.append(real_stroke_node)
+ data = []
+ # read gradients data
+ for gradient in gradient_list:
+ # parse gradient stops
+ stop_data = read_stop_gradient(gradient)
+ data.append(stop_data)
+ return data
+
+ # called when the extension is running.
+ def effect(self):
+ self.doc_selected_gradients = self.get_selected_gradients_data()
+ try:
+ app = MainWindow(self)
+ Gtk.main()
+ except Exception as e:
+ import traceback
+ show_errormsg(e)
+ show_errormsg(traceback.print_exc())
+
+
+if __name__ == '__main__':
+ GradientSaver().run()
diff --git a/extensions/fablabchemnitz/gradient_saver/icon.svg b/extensions/fablabchemnitz/gradient_saver/icon.svg
new file mode 100644
index 00000000..1ef42700
--- /dev/null
+++ b/extensions/fablabchemnitz/gradient_saver/icon.svg
@@ -0,0 +1,678 @@
+
+
+
+
diff --git a/extensions/fablabchemnitz/my-gradients.svg b/extensions/fablabchemnitz/my-gradients.svg
new file mode 100644
index 00000000..98aff73c
--- /dev/null
+++ b/extensions/fablabchemnitz/my-gradients.svg
@@ -0,0 +1,4 @@
+
+