This repository has been archived on 2023-03-25. You can view files and clone it, but cannot push or open issues or pull requests.
mightyscape-1.1-deprecated/extensions/fablabchemnitz/gradient_saver/gradient_saver.py
2021-07-23 02:36:56 +02:00

386 lines
16 KiB
Python

#! /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.values():
style = Style(Style.parse_str(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()