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 + Gradient Saver + False + center + 600 + 400 + icon.svg + north + + + + + + + True + True + + + + True + False + 400 + 250 + + + 600 + 180 + True + True + + + True + False + none + + + True + False + 30 + 10 + 2 + vertical + 20 + + + True + False + 10 + 18 + + + 150 + 42 + True + True + False + + + False + True + 0 + + + + + 325 + 42 + True + True + 25 + e.g Beautiful Color + + + False + True + 1 + + + + + False + False + 1 + + + + + + + + + 130 + + + + + 580 + 32 + True + False + 20 + 10 + end + + + Cancel + True + True + True + + + + True + True + 0 + + + + + Save + True + True + True + + + + True + True + 1 + + + + + 318 + + + + + 600 + 100 + True + False + 10 + top + + + 55 + True + False + 30 + 21 + gtk-info + 6 + + + False + True + 0 + + + + + 250 + True + False + 30 + 14 + <b>Hint:</b> +Select at least one object that use gradient color in order to add those gradient to your library. Selected gradient object will appear below. You can give a name for your gradients to help you recoginize it later. + True + True + + + False + True + 1 + + + + + + + 550 + 20 + True + False + 45 + 50 + 10 + 100 + + + True + False + <b>Preview</b> + True + + + False + True + 0 + + + + + True + False + <b>Gradient Name</b> + True + + + False + True + 1 + + + + + 100 + + + + + + + True + False + Add New + + + False + + + + + True + False + + + 580 + 32 + True + False + 20 + 6 + bottom + end + + + Remove + True + True + True + True + + + + True + True + 0 + + + + + Load + True + True + True + True + + + + True + True + 1 + + + + + 318 + + + + + 600 + 100 + True + False + 10 + top + + + 55 + True + False + 30 + 21 + gtk-info + 6 + + + False + True + 0 + + + + + 250 + True + False + 30 + 14 + <b>Hint:</b> +Select at least one object that use gradient color in order to add those gradient to your library. Selected gradient object will appear below. You can give a name for your gradients to help you recoginize it later. + True + True + + + False + True + 1 + + + + + + + 550 + 20 + True + False + 45 + 50 + 10 + 100 + + + True + False + <b>Your Current Gradient</b> + True + + + False + True + 0 + + + + + 100 + + + + + 600 + 180 + True + True + + + True + False + none + + + True + False + start + 10 + 10 + 30 + 10 + 2 + 2 + none + False + + + 60 + 42 + True + True + start + start + + + True + False + 5 + top + + + 42 + True + True + False + True + + + False + True + 0 + + + + + 100 + 42 + True + True + False + + + False + True + 1 + + + + + 42 + True + False + ijo trans + True + char + 25 + + + False + True + 2 + + + + + + + + + + + + + 130 + + + + + 1 + + + + + True + False + Load or Remove + + + 1 + False + + + + + True + False + + + 317 + 300 + True + False + <span font="12"><b>Version $VERSION</b></span> + True + True + + + 137 + -84 + + + + + 100 + 80 + True + False + icon.svg + + + 10 + 32 + + + + + 100 + 80 + True + False + <span font="12"><b>Gradient Saver</b></span> + True + + + 69 + 225 + + + + + 307 + 80 + True + False + <span>Gradient Saver is an extension for Inkscape that will help you to organize your favorite gradients. This extension created with love by <a href="#">Sofyan</a> &amp; <a href="https://raniaamina.id">Rania Amina</a> and fully supported by Gimpscape ID Community. + +Project Repository: +<a href="#"><b>https://github.com/artemtech/inkscape-gradient-saver</b></a></span> + True + True + + + 243 + 82 + + + + + 580 + 32 + True + False + 20 + 10 + end + + + Close + True + True + True + + + + True + True + 1 + + + + + 318 + + + + + 2 + + + + + True + False + About + + + 2 + False + + + + + + + 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 @@ + + + + + Gradient Saver Icon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Gradient Saver Icon + + + Hadiid Pratama + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + +