diff options
Diffstat (limited to 'lib/extensions')
| -rw-r--r-- | lib/extensions/auto_satin.py | 56 | ||||
| -rw-r--r-- | lib/extensions/base.py | 22 | ||||
| -rw-r--r-- | lib/extensions/commands.py | 118 | ||||
| -rw-r--r-- | lib/extensions/input.py | 2 | ||||
| -rw-r--r-- | lib/extensions/layer_commands.py | 4 | ||||
| -rw-r--r-- | lib/extensions/lettering.py | 183 | ||||
| -rw-r--r-- | lib/extensions/object_commands.py | 9 | ||||
| -rw-r--r-- | lib/extensions/print_pdf.py | 157 |
8 files changed, 190 insertions, 361 deletions
diff --git a/lib/extensions/auto_satin.py b/lib/extensions/auto_satin.py index f846ac6b..b7cee83b 100644 --- a/lib/extensions/auto_satin.py +++ b/lib/extensions/auto_satin.py @@ -2,11 +2,8 @@ import sys import inkex -from ..elements import SatinColumn from ..i18n import _ from ..stitches.auto_satin import auto_satin -from ..svg import get_correction_transform -from ..svg.tags import SVG_GROUP_TAG, INKSCAPE_LABEL from .commands import CommandsExtension @@ -37,22 +34,6 @@ class AutoSatin(CommandsExtension): if command is not None: return command.target_point - def effect(self): - if not self.check_selection(): - return - - if self.options.preserve_order: - # when preservering order, auto_satin() takes care of putting the - # newly-created elements into the existing group nodes in the SVG - # DOM - new_elements, trim_indices = self.auto_satin() - else: - group = self.create_group() - new_elements, trim_indices = self.auto_satin() - self.add_elements(group, new_elements) - - self.add_trims(new_elements, trim_indices) - def check_selection(self): if not self.get_elements(): return @@ -64,39 +45,10 @@ class AutoSatin(CommandsExtension): return True - def create_group(self): - first = self.elements[0].node - parent = first.getparent() - insert_index = parent.index(first) - group = inkex.etree.Element(SVG_GROUP_TAG, { - "transform": get_correction_transform(parent, child=True) - }) - parent.insert(insert_index, group) - - return group + def effect(self): + if not self.check_selection(): + return - def auto_satin(self): starting_point = self.get_starting_point() ending_point = self.get_ending_point() - return auto_satin(self.elements, self.options.preserve_order, starting_point, ending_point) - - def add_elements(self, group, new_elements): - for i, element in enumerate(new_elements): - if isinstance(element, SatinColumn): - element.node.set("id", self.uniqueId("autosatin")) - - # L10N Label for a satin column created by Auto-Route Satin Columns extension - element.node.set(INKSCAPE_LABEL, _("AutoSatin %d") % (i + 1)) - else: - element.node.set("id", self.uniqueId("autosatinrun")) - - # L10N Label for running stitch (underpathing) created by Auto-Route Satin Columns extension - element.node.set(INKSCAPE_LABEL, _("AutoSatin Running Stitch %d") % (i + 1)) - - group.append(element.node) - - def add_trims(self, new_elements, trim_indices): - if self.options.trim and trim_indices: - self.ensure_symbol("trim") - for i in trim_indices: - self.add_commands(new_elements[i], ["trim"]) + auto_satin(self.elements, self.options.preserve_order, starting_point, ending_point, self.options.trim) diff --git a/lib/extensions/base.py b/lib/extensions/base.py index 6b846aeb..440a5413 100644 --- a/lib/extensions/base.py +++ b/lib/extensions/base.py @@ -1,8 +1,8 @@ from collections import MutableMapping from copy import deepcopy import json -import re import os +import re import inkex from stringcase import snakecase @@ -10,6 +10,7 @@ from stringcase import snakecase from ..commands import layer_commands from ..elements import EmbroideryElement, nodes_to_elements from ..i18n import _ +from ..svg import generate_unique_id from ..svg.tags import SVG_GROUP_TAG, INKSCAPE_GROUPMODE, SVG_DEFS_TAG, EMBROIDERABLE_TAGS @@ -122,10 +123,13 @@ class InkstitchExtension(inkex.Effect): def no_elements_error(self): if self.selected: - inkex.errormsg(_("No embroiderable paths selected.")) + # l10n This was previously: "No embroiderable paths selected." + inkex.errormsg(_("Ink/Stitch doesn't know how to work with any of the objects you've selected.") + "\n") else: - inkex.errormsg(_("No embroiderable paths found in document.")) - inkex.errormsg(_("Tip: use Path -> Object to Path to convert non-paths.")) + inkex.errormsg(_("There are no objects in the entire document that Ink/Stitch knows how to work with.") + "\n") + + inkex.errormsg(_("Ink/Stitch only knows how to work with paths. It can't work with objects like text, rectangles, or circles.") + "\n") + inkex.errormsg(_("Tip: select some objects and use Path -> Object to Path to convert them to paths.") + "\n") def descendants(self, node, selected=False): nodes = [] @@ -192,15 +196,7 @@ class InkstitchExtension(inkex.Effect): def uniqueId(self, prefix, make_new_id=True): """Override inkex.Effect.uniqueId with a nicer naming scheme.""" - i = 1 - while True: - new_id = "%s%d" % (prefix, i) - if new_id not in self.doc_ids: - break - i += 1 - self.doc_ids[new_id] = 1 - - return new_id + return generate_unique_id(self.document, prefix) def parse(self): """Override inkex.Effect.parse to add Ink/Stitch xml namespace""" diff --git a/lib/extensions/commands.py b/lib/extensions/commands.py index 07b450e1..86e291fd 100644 --- a/lib/extensions/commands.py +++ b/lib/extensions/commands.py @@ -1,16 +1,4 @@ -import os -import inkex -from copy import deepcopy -from random import random - - from .base import InkstitchExtension -from ..utils import get_bundled_dir, cache -from ..commands import get_command_description -from ..i18n import _ -from ..svg.tags import SVG_DEFS_TAG, SVG_PATH_TAG, CONNECTION_START, CONNECTION_END, \ - CONNECTOR_TYPE, INKSCAPE_LABEL, SVG_GROUP_TAG, SVG_USE_TAG, XLINK_HREF -from ..svg import get_correction_transform class CommandsExtension(InkstitchExtension): @@ -20,109 +8,3 @@ class CommandsExtension(InkstitchExtension): InkstitchExtension.__init__(self, *args, **kwargs) for command in self.COMMANDS: self.OptionParser.add_option("--%s" % command, type="inkbool") - - @property - def symbols_path(self): - return os.path.join(get_bundled_dir("symbols"), "inkstitch.svg") - - @property - @cache - def symbols_svg(self): - with open(self.symbols_path) as symbols_file: - return inkex.etree.parse(symbols_file) - - @property - @cache - def symbol_defs(self): - return self.symbols_svg.find(SVG_DEFS_TAG) - - @property - @cache - def defs(self): - return self.document.find(SVG_DEFS_TAG) - - def ensure_symbol(self, command): - path = "./*[@id='inkstitch_%s']" % command - if self.defs.find(path) is None: - self.defs.append(deepcopy(self.symbol_defs.find(path))) - - def add_connector(self, symbol, element): - # I'd like it if I could position the connector endpoint nicely but inkscape just - # moves it to the element's center immediately after the extension runs. - start_pos = (symbol.get('x'), symbol.get('y')) - end_pos = element.shape.centroid - - path = inkex.etree.Element(SVG_PATH_TAG, - { - "id": self.uniqueId("connector"), - "d": "M %s,%s %s,%s" % (start_pos[0], start_pos[1], end_pos.x, end_pos.y), - "style": "stroke:#000000;stroke-width:1px;stroke-opacity:0.5;fill:none;", - CONNECTION_START: "#%s" % symbol.get('id'), - CONNECTION_END: "#%s" % element.node.get('id'), - CONNECTOR_TYPE: "polyline", - - # l10n: the name of the line that connects a command to the object it applies to - INKSCAPE_LABEL: _("connector") - } - ) - - symbol.getparent().insert(0, path) - - def get_command_pos(self, element, index, total): - # Put command symbols 30 pixels out from the shape, spaced evenly around it. - - # get a line running 30 pixels out from the shape - outline = element.shape.buffer(30).exterior - - # pick this item's spot arond the outline and perturb it a bit to avoid - # stacking up commands if they run the extension multiple times - position = index / float(total) - position += random() * 0.1 - - return outline.interpolate(position, normalized=True) - - def remove_legacy_param(self, element, command): - if command == "trim" or command == "stop": - # If they had the old "TRIM after" or "STOP after" attributes set, - # automatically delete them. THe new commands will do the same - # thing. - # - # If we didn't delete these here, then things would get confusing. - # If the user were to delete a "trim" symbol added by this extension - # but the "embroider_trim_after" attribute is still set, then the - # trim would keep happening. - - attribute = "embroider_%s_after" % command - - if attribute in element.node.attrib: - del element.node.attrib[attribute] - - def add_commands(self, element, commands): - for i, command in enumerate(commands): - self.remove_legacy_param(element, command) - - pos = self.get_command_pos(element, i, len(commands)) - - group = inkex.etree.SubElement(element.node.getparent(), SVG_GROUP_TAG, - { - "id": self.uniqueId("group"), - INKSCAPE_LABEL: _("Ink/Stitch Command") + ": %s" % get_command_description(command), - "transform": get_correction_transform(element.node) - } - ) - - symbol = inkex.etree.SubElement(group, SVG_USE_TAG, - { - "id": self.uniqueId("use"), - XLINK_HREF: "#inkstitch_%s" % command, - "height": "100%", - "width": "100%", - "x": str(pos.x), - "y": str(pos.y), - - # l10n: the name of a command symbol (example: scissors icon for trim command) - INKSCAPE_LABEL: _("command marker"), - } - ) - - self.add_connector(symbol, element) diff --git a/lib/extensions/input.py b/lib/extensions/input.py index 975ba838..0ca85df5 100644 --- a/lib/extensions/input.py +++ b/lib/extensions/input.py @@ -33,7 +33,7 @@ class Input(object): # rename the Stitch Plan layer so that it doesn't get overwritten by Embroider layer = svg.find(".//*[@id='__inkstitch_stitch_plan__']") - layer.set(INKSCAPE_LABEL, os.path.basename(embroidery_file)) + layer.set(INKSCAPE_LABEL, os.path.basename(embroidery_file.decode("UTF-8"))) layer.attrib.pop('id') # Shift the design so that its origin is at the center of the canvas diff --git a/lib/extensions/layer_commands.py b/lib/extensions/layer_commands.py index 3a746fcf..c124ec95 100644 --- a/lib/extensions/layer_commands.py +++ b/lib/extensions/layer_commands.py @@ -1,6 +1,6 @@ import inkex -from ..commands import LAYER_COMMANDS, get_command_description +from ..commands import LAYER_COMMANDS, get_command_description, ensure_symbol from ..i18n import _ from ..svg import get_correction_transform from ..svg.tags import SVG_USE_TAG, INKSCAPE_LABEL, XLINK_HREF @@ -21,7 +21,7 @@ class LayerCommands(CommandsExtension): correction_transform = get_correction_transform(self.current_layer, child=True) for i, command in enumerate(commands): - self.ensure_symbol(command) + ensure_symbol(command) inkex.etree.SubElement(self.current_layer, SVG_USE_TAG, { diff --git a/lib/extensions/lettering.py b/lib/extensions/lettering.py index b6d67c0b..a2a729b5 100644 --- a/lib/extensions/lettering.py +++ b/lib/extensions/lettering.py @@ -5,19 +5,23 @@ import json import os import sys +import appdirs import inkex import wx from ..elements import nodes_to_elements -from ..gui import PresetsPanel, SimulatorPreview +from ..gui import PresetsPanel, SimulatorPreview, info_dialog, SubtitleComboBox from ..i18n import _ -from ..lettering import Font +from ..lettering import Font, FontError +from ..svg import get_correction_transform from ..svg.tags import SVG_PATH_TAG, SVG_GROUP_TAG, INKSCAPE_LABEL, INKSTITCH_LETTERING -from ..utils import get_bundled_dir, DotDict +from ..utils import get_bundled_dir, DotDict, cache from .commands import CommandsExtension class LetteringFrame(wx.Frame): + DEFAULT_FONT = "small_font" + def __init__(self, *args, **kwargs): # begin wxGlade: MyFrame.__init__ self.group = kwargs.pop('group') @@ -29,23 +33,28 @@ class LetteringFrame(wx.Frame): self.preview = SimulatorPreview(self, target_duration=1) self.presets_panel = PresetsPanel(self) - self.load_settings() - # options self.options_box = wx.StaticBox(self, wx.ID_ANY, label=_("Options")) self.back_and_forth_checkbox = wx.CheckBox(self, label=_("Stitch lines of text back and forth")) - self.back_and_forth_checkbox.SetValue(self.settings.back_and_forth) - self.Bind(wx.EVT_CHECKBOX, lambda event: self.on_change("back_and_forth", event)) + self.back_and_forth_checkbox.Bind(wx.EVT_CHECKBOX, lambda event: self.on_change("back_and_forth", event)) + + self.trim_checkbox = wx.CheckBox(self, label=_("Add trims")) + self.trim_checkbox.Bind(wx.EVT_CHECKBOX, lambda event: self.on_change("trim", event)) # text editor self.text_editor_box = wx.StaticBox(self, wx.ID_ANY, label=_("Text")) - self.font_chooser = wx.ComboBox(self, wx.ID_ANY) self.update_font_list() + self.font_chooser = SubtitleComboBox(self, wx.ID_ANY, choices=self.get_font_names(), + subtitles=self.get_font_descriptions(), style=wx.CB_READONLY) + self.font_chooser.Bind(wx.EVT_COMBOBOX, self.on_font_changed) + + self.scale_spinner = wx.SpinCtrl(self, wx.ID_ANY, min=100, max=100, initial=100) + self.scale_spinner.Bind(wx.EVT_SPINCTRL, lambda event: self.on_change("scale", event)) - self.text_editor = wx.TextCtrl(self, style=wx.TE_MULTILINE | wx.TE_DONTWRAP, value=self.settings.text) - self.Bind(wx.EVT_TEXT, lambda event: self.on_change("text", event)) + self.text_editor = wx.TextCtrl(self, style=wx.TE_MULTILINE | wx.TE_DONTWRAP) + self.text_editor.Bind(wx.EVT_TEXT, lambda event: self.on_change("text", event)) self.cancel_button = wx.Button(self, wx.ID_ANY, _("Cancel")) self.cancel_button.Bind(wx.EVT_BUTTON, self.cancel) @@ -55,23 +64,38 @@ class LetteringFrame(wx.Frame): self.apply_button.Bind(wx.EVT_BUTTON, self.apply) self.__do_layout() - # end wxGlade + + self.load_settings() + self.apply_settings() def load_settings(self): + """Load the settings saved into the SVG group element""" + + self.settings = DotDict({ + "text": u"", + "back_and_forth": True, + "font": None, + "scale": 100 + }) + try: if INKSTITCH_LETTERING in self.group.attrib: - self.settings = DotDict(json.loads(b64decode(self.group.get(INKSTITCH_LETTERING)))) + self.settings.update(json.loads(b64decode(self.group.get(INKSTITCH_LETTERING)))) return except (TypeError, ValueError): pass - self.settings = DotDict({ - "text": u"", - "back_and_forth": True, - "font": "small_font" - }) + def apply_settings(self): + """Make the settings in self.settings visible in the UI.""" + self.back_and_forth_checkbox.SetValue(bool(self.settings.back_and_forth)) + self.trim_checkbox.SetValue(bool(self.settings.trim)) + self.set_initial_font(self.settings.font) + self.text_editor.SetValue(self.settings.text) + self.scale_spinner.SetValue(self.settings.scale) def save_settings(self): + """Save the settings into the SVG group element.""" + # We base64 encode the string before storing it in an XML attribute. # In theory, lxml should properly html-encode the string, using HTML # entities like as necessary. However, we've found that Inkscape @@ -82,19 +106,102 @@ class LetteringFrame(wx.Frame): # https://bugs.launchpad.net/inkscape/+bug/1804346 self.group.set(INKSTITCH_LETTERING, b64encode(json.dumps(self.settings))) + def update_font_list(self): + font_paths = { + get_bundled_dir("fonts"), + os.path.expanduser("~/.inkstitch/fonts"), + os.path.join(appdirs.user_config_dir('inkstitch'), 'fonts'), + } + + self.fonts = {} + self.fonts_by_id = {} + + for font_path in font_paths: + try: + font_dirs = os.listdir(font_path) + except OSError: + continue + + try: + for font_dir in font_dirs: + font = Font(os.path.join(font_path, font_dir)) + self.fonts[font.name] = font + self.fonts_by_id[font.id] = font + except FontError: + pass + + if len(self.fonts) == 0: + info_dialog(self, _("Unable to find any fonts! Please try reinstalling Ink/Stitch.")) + self.cancel() + + def get_font_names(self): + font_names = [font.name for font in self.fonts.itervalues()] + font_names.sort() + + return font_names + + def get_font_descriptions(self): + return {font.name: font.description for font in self.fonts.itervalues()} + + def set_initial_font(self, font_id): + if font_id: + if font_id not in self.fonts_by_id: + message = '''This text was created using the font "%s", but Ink/Stitch can't find that font. ''' \ + '''A default font will be substituted.''' + info_dialog(self, _(message) % font_id) + + try: + self.font_chooser.SetValueByUser(self.fonts_by_id[font_id].name) + except KeyError: + self.font_chooser.SetValueByUser(self.default_font.name) + + self.on_font_changed() + + @property + @cache + def default_font(self): + try: + return self.fonts[self.DEFAULT_FONT] + except KeyError: + return self.fonts.values()[0] + def on_change(self, attribute, event): self.settings[attribute] = event.GetEventObject().GetValue() self.preview.update() + def on_font_changed(self, event=None): + font = self.fonts.get(self.font_chooser.GetValue(), self.default_font) + self.settings.font = font.id + self.scale_spinner.SetRange(int(font.min_scale * 100), int(font.max_scale * 100)) + self.update_preview() + + def update_preview(self, event=None): + self.preview.update() + + def update_lettering(self): + del self.group[:] + + if self.settings.scale == 100: + destination_group = self.group + else: + destination_group = inkex.etree.SubElement(self.group, SVG_GROUP_TAG, { + # L10N The user has chosen to scale the text by some percentage + # (50%, 200%, etc). If you need to use the percentage symbol, + # make sure to double it (%%). + INKSCAPE_LABEL: _("Text scale %s%%") % self.settings.scale + }) + + font = self.fonts.get(self.font_chooser.GetValue(), self.default_font) + font.render_text(self.settings.text, destination_group, back_and_forth=self.settings.back_and_forth, trim=self.settings.trim) + + if self.settings.scale != 100: + destination_group.attrib['transform'] = 'scale(%s)' % (self.settings.scale / 100.0) + def generate_patches(self, abort_early=None): patches = [] - font_path = os.path.join(get_bundled_dir("fonts"), self.settings.font) - font = Font(font_path) - try: - lines = font.render_text(self.settings.text, back_and_forth=self.settings.back_and_forth) - self.group[:] = lines + self.update_lettering() elements = nodes_to_elements(self.group.iterdescendants(SVG_PATH_TAG)) for element in elements: @@ -113,17 +220,17 @@ class LetteringFrame(wx.Frame): return patches - def update_font_list(self): - pass - def get_preset_data(self): # called by self.presets_panel - preset = {} - return preset + settings = dict(self.settings) + del settings["text"] + return settings - def apply_preset_data(self): - # called by self.presets_panel - return + def apply_preset_data(self, preset_data): + settings = DotDict(preset_data) + settings["text"] = self.settings.text + self.settings = settings + self.apply_settings() def get_preset_suite_name(self): # called by self.presets_panel @@ -131,7 +238,7 @@ class LetteringFrame(wx.Frame): def apply(self, event): self.preview.disable() - self.generate_patches() + self.update_lettering() self.save_settings() self.close() @@ -149,11 +256,18 @@ class LetteringFrame(wx.Frame): outer_sizer = wx.BoxSizer(wx.VERTICAL) options_sizer = wx.StaticBoxSizer(self.options_box, wx.VERTICAL) - options_sizer.Add(self.back_and_forth_checkbox, 1, wx.EXPAND | wx.LEFT | wx.TOP | wx.RIGHT | wx.BOTTOM, 10) + options_sizer.Add(self.back_and_forth_checkbox, 1, wx.EXPAND | wx.LEFT | wx.TOP | wx.RIGHT, 5) + options_sizer.Add(self.trim_checkbox, 1, wx.EXPAND | wx.LEFT | wx.TOP | wx.RIGHT | wx.BOTTOM, 5) outer_sizer.Add(options_sizer, 0, wx.EXPAND | wx.LEFT | wx.TOP | wx.RIGHT, 10) + font_sizer = wx.BoxSizer(wx.HORIZONTAL) + font_sizer.Add(self.font_chooser, 1, wx.EXPAND, 0) + font_sizer.Add(wx.StaticText(self, wx.ID_ANY, "Scale"), 0, wx.LEFT | wx.ALIGN_CENTRE_VERTICAL, 20) + font_sizer.Add(self.scale_spinner, 0, wx.LEFT, 10) + font_sizer.Add(wx.StaticText(self, wx.ID_ANY, "%"), 0, wx.LEFT | wx.ALIGN_CENTRE_VERTICAL, 3) + text_editor_sizer = wx.StaticBoxSizer(self.text_editor_box, wx.VERTICAL) - text_editor_sizer.Add(self.font_chooser, 0, wx.ALL, 10) + text_editor_sizer.Add(font_sizer, 0, wx.ALL | wx.EXPAND, 10) text_editor_sizer.Add(self.text_editor, 1, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, 10) outer_sizer.Add(text_editor_sizer, 1, wx.EXPAND | wx.LEFT | wx.TOP | wx.RIGHT, 10) @@ -210,7 +324,8 @@ class Lettering(CommandsExtension): else: self.ensure_current_layer() return inkex.etree.SubElement(self.current_layer, SVG_GROUP_TAG, { - INKSCAPE_LABEL: _("Ink/Stitch Lettering") + INKSCAPE_LABEL: _("Ink/Stitch Lettering"), + "transform": get_correction_transform(self.current_layer, child=True) }) def effect(self): diff --git a/lib/extensions/object_commands.py b/lib/extensions/object_commands.py index 47fb361d..d33ab2ba 100644 --- a/lib/extensions/object_commands.py +++ b/lib/extensions/object_commands.py @@ -1,8 +1,8 @@ import inkex -from .commands import CommandsExtension -from ..commands import OBJECT_COMMANDS +from ..commands import OBJECT_COMMANDS, add_commands from ..i18n import _ +from .commands import CommandsExtension class ObjectCommands(CommandsExtension): @@ -24,14 +24,11 @@ class ObjectCommands(CommandsExtension): inkex.errormsg(_("Please choose one or more commands to attach.")) return - for command in commands: - self.ensure_symbol(command) - # Each object (node) in the SVG may correspond to multiple Elements of different # types (e.g. stroke + fill). We only want to process each one once. seen_nodes = set() for element in self.elements: if element.node not in seen_nodes: - self.add_commands(element, commands) + add_commands(element, commands) seen_nodes.add(element.node) diff --git a/lib/extensions/print_pdf.py b/lib/extensions/print_pdf.py index 4913a32a..befb7861 100644 --- a/lib/extensions/print_pdf.py +++ b/lib/extensions/print_pdf.py @@ -1,27 +1,27 @@ -import sys +from copy import deepcopy +from datetime import date +import errno +import json +import logging import os -from threading import Thread import socket -import errno +import sys +from threading import Thread import time -import logging -from copy import deepcopy -import wx + import appdirs -import json +from flask import Flask, request, Response, send_from_directory, jsonify import inkex from jinja2 import Environment, FileSystemLoader, select_autoescape -from datetime import date -from flask import Flask, request, Response, send_from_directory, jsonify -import webbrowser import requests -from .base import InkstitchExtension -from ..i18n import _, translation as inkstitch_translation +from ..gui import open_url +from ..i18n import translation as inkstitch_translation +from ..stitch_plan import patches_to_stitch_plan from ..svg import render_stitch_plan from ..svg.tags import INKSCAPE_GROUPMODE -from ..stitch_plan import patches_to_stitch_plan from ..threads import ThreadCatalog +from .base import InkstitchExtension def datetimeformat(value, format='%Y/%m/%d'): @@ -51,42 +51,6 @@ def save_defaults(defaults): json.dump(defaults, defaults_file) -def open_url(url): - # Avoid spurious output from xdg-open. Any output on stdout will crash - # inkscape. - null = open(os.devnull, 'w') - old_stdout = os.dup(sys.stdout.fileno()) - os.dup2(null.fileno(), sys.stdout.fileno()) - - if getattr(sys, 'frozen', False): - - # PyInstaller sets LD_LIBRARY_PATH. We need to temporarily clear it - # to avoid confusing xdg-open, which webbrowser will run. - - # The following code is adapted from PyInstaller's documentation - # http://pyinstaller.readthedocs.io/en/stable/runtime-information.html - - old_environ = dict(os.environ) # make a copy of the environment - lp_key = 'LD_LIBRARY_PATH' # for Linux and *BSD. - lp_orig = os.environ.get(lp_key + '_ORIG') # pyinstaller >= 20160820 has this - if lp_orig is not None: - os.environ[lp_key] = lp_orig # restore the original, unmodified value - else: - os.environ.pop(lp_key, None) # last resort: remove the env var - - webbrowser.open(url) - - # restore the old environ - os.environ.clear() - os.environ.update(old_environ) - else: - webbrowser.open(url) - - # restore file descriptors - os.dup2(old_stdout, sys.stdout.fileno()) - os.close(old_stdout) - - class PrintPreviewServer(Thread): def __init__(self, *args, **kwargs): self.html = kwargs.pop('html') @@ -96,7 +60,6 @@ class PrintPreviewServer(Thread): self.realistic_color_block_svgs = kwargs.pop('realistic_color_block_svgs') Thread.__init__(self, *args, **kwargs) self.daemon = True - self.last_request_time = None self.shutting_down = False self.__setup_app() @@ -111,16 +74,6 @@ class PrintPreviewServer(Thread): self.__set_resources_path() self.app = Flask(__name__) - @self.app.before_request - def request_started(): - self.last_request_time = time.time() - - @self.app.before_first_request - def start_watcher(): - self.watcher_thread = Thread(target=self.watch) - self.watcher_thread.daemon = True - self.watcher_thread.start() - @self.app.route('/') def index(): return self.html @@ -129,29 +82,12 @@ class PrintPreviewServer(Thread): def shutdown(): self.shutting_down = True request.environ.get('werkzeug.server.shutdown')() - return _('Closing...') + '<br/><br/>' + _('It is safe to close this window now.') + return "shutting down" @self.app.route('/resources/<path:resource>', methods=['GET']) def resources(resource): return send_from_directory(self.resources_path, resource, cache_timeout=1) - @self.app.route('/ping') - def ping(): - # Javascript is letting us know it's still there. This resets self.last_request_time. - return "pong" - - @self.app.route('/printing/start') - def printing_start(): - # temporarily turn off the watcher while the print dialog is up, - # because javascript will be frozen - self.last_request_time = None - return "OK" - - @self.app.route('/printing/end') - def printing_end(): - # nothing to do here -- request_started() will restart the watcher - return "OK" - @self.app.route('/settings/<field_name>', methods=['POST']) def set_field(field_name): self.metadata[field_name] = request.json['value'] @@ -215,21 +151,6 @@ class PrintPreviewServer(Thread): # the context of a flask request, so we'll just make one requests.post("http://%s:%s/shutdown" % (self.host, self.port)) - def watch(self): - try: - while True: - time.sleep(1) - if self.shutting_down: - break - - if self.last_request_time is not None and \ - (time.time() - self.last_request_time) > 3: - self.stop() - break - except BaseException: - # seems like sometimes this thread blows up during shutdown - pass - def disable_logging(self): logging.getLogger('werkzeug').setLevel(logging.ERROR) @@ -252,42 +173,6 @@ class PrintPreviewServer(Thread): break -class PrintInfoFrame(wx.Frame): - def __init__(self, *args, **kwargs): - self.print_server = kwargs.pop("print_server") - wx.Frame.__init__(self, *args, **kwargs) - - panel = wx.Panel(self) - sizer = wx.BoxSizer(wx.VERTICAL) - - message = _("A print preview has been opened in your web browser. " - "This window will stay open in order to communicate with the JavaScript code running in your browser.\n\n" - "This window will close after you close the print preview in your browser, or you can close it manually if necessary.") - text = wx.StaticText(panel, label=message) - font = wx.Font(14, wx.DEFAULT, wx.NORMAL, wx.NORMAL) - text.SetFont(font) - sizer.Add(text, proportion=1, flag=wx.ALL | wx.EXPAND, border=20) - - stop_button = wx.Button(panel, id=wx.ID_CLOSE) - stop_button.Bind(wx.EVT_BUTTON, self.close_button_clicked) - sizer.Add(stop_button, proportion=0, flag=wx.ALIGN_CENTER | wx.ALL, border=10) - - panel.SetSizer(sizer) - panel.Layout() - - self.timer = wx.PyTimer(self.__watcher) - self.timer.Start(250) - - def close_button_clicked(self, event): - self.print_server.stop() - - def __watcher(self): - if not self.print_server.is_alive(): - self.timer.Stop() - self.timer = None - self.Destroy() - - class Print(InkstitchExtension): def build_environment(self): if getattr(sys, 'frozen', False): @@ -410,10 +295,12 @@ class Print(InkstitchExtension): ) print_server.start() - time.sleep(1) - open_url("http://%s:%s/" % (print_server.host, print_server.port)) + # Wait for print_server.host and print_server.port to be populated. + # Hacky, but Flask doesn't have an option for a callback to be run + # after startup. + time.sleep(0.5) - app = wx.App() - info_frame = PrintInfoFrame(None, title=_("Ink/Stitch Print"), size=(450, 350), print_server=print_server) - info_frame.Show() - app.MainLoop() + browser_window = open_url("http://%s:%s/" % (print_server.host, print_server.port)) + browser_window.wait() + print_server.stop() + print_server.join() |
