386 lines
16 KiB
Python
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:
|
|
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()
|