move print PDF back to web browser (#2849)

* move print PDF back to web browser

* fix line wrapping for macOS

---------

Co-authored-by: Kaalleen <reni@allenka.de>
pull/2655/head^2 dev-build-kaalleen-remove-electron-entirely
Lex Neva 2024-04-24 22:38:32 -04:00 zatwierdzone przez GitHub
rodzic 129dfa019b
commit 3b16235821
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
4 zmienionych plików z 179 dodań i 23 usunięć

Wyświetl plik

@ -9,25 +9,27 @@ import os
import socket
import sys
import time
import webbrowser
from contextlib import closing
from copy import deepcopy
from datetime import date
from threading import Thread
from contextlib import closing
import appdirs
import wx
from flask import Flask, Response, jsonify, request, send_from_directory
from jinja2 import Environment, FileSystemLoader, select_autoescape
from lxml import etree
from werkzeug.serving import make_server
from ..gui import open_url
from ..i18n import get_languages
from .base import InkstitchExtension
from ..debug import debug
from ..i18n import _, get_languages
from ..i18n import translation as inkstitch_translation
from ..stitch_plan import stitch_groups_to_stitch_plan
from ..svg import render_stitch_plan
from ..svg.tags import INKSCAPE_GROUPMODE
from ..threads import ThreadCatalog
from .base import InkstitchExtension
def datetimeformat(value, format='%Y/%m/%d'):
@ -57,6 +59,42 @@ 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')
@ -66,8 +104,11 @@ 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.flask_server = None
self.server_thread = None
self.started = False
self.__setup_app()
@ -89,14 +130,33 @@ class PrintPreviewServer(Thread):
self.app = Flask(__name__)
self.watcher_thread = Thread(target=self.watch)
self.watcher_thread.daemon = True
self.watcher_thread.start()
@self.app.before_request
def request_started():
self.last_request_time = time.time()
@self.app.route('/')
def index():
return self.html
@self.app.route('/shutdown', methods=['POST'])
def shutdown():
self.shutting_down = True
return _('Closing...') + '<br/><br/>' + _('It is safe to close this window now.')
@self.app.route('/resources/<path:resource>', methods=['GET'])
def resources(resource):
return send_from_directory(self.resources_path, resource, max_age=1)
@self.app.route('/ping')
def ping():
debug.log("got a ping")
# Javascript is letting us know it's still there. This resets self.last_request_time.
return "pong"
@self.app.route('/settings/<field_name>', methods=['POST'])
def set_field(field_name):
self.metadata[field_name] = request.json['value']
@ -155,10 +215,40 @@ class PrintPreviewServer(Thread):
def get_realistic_overview():
return Response(self.realistic_overview_svg, mimetype='image/svg+xml')
@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"
def stop(self):
self.flask_server.shutdown()
self.server_thread.join()
def watch(self):
try:
while True:
time.sleep(1)
if self.shutting_down:
debug.log("watcher thread: shutting down")
self.stop()
break
if self.last_request_time is not None and (time.time() - self.last_request_time) > 3:
debug.log("watcher thread: timed out, stopping")
self.stop()
break
except BaseException:
# seems like sometimes this thread blows up during shutdown
debug.log(f"exception in watcher {sys.exc_info()}")
pass
def disable_logging(self):
logging.getLogger('werkzeug').setLevel(logging.ERROR)
@ -180,6 +270,52 @@ class PrintPreviewServer(Thread):
self.flask_server = make_server(self.host, self.port, self.app)
self.server_thread = Thread(target=self.flask_server.serve_forever)
self.server_thread.start()
self.started = True
self.server_thread.join()
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)
self.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."
)
self.text = wx.StaticText(panel, label=self.message)
font = wx.Font(14, wx.DEFAULT, wx.NORMAL, wx.NORMAL)
self.text.SetFont(font)
self.Bind(wx.EVT_SIZE, self.resize)
sizer.Add(self.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 resize(self, event=None):
self.text.SetLabel(self.message)
self.text.Wrap(self.GetSize().width - 35)
self.Layout()
def close_button_clicked(self, event):
self.print_server.stop()
def __watcher(self):
if self.print_server.started and not self.print_server.is_alive():
self.timer.Stop()
self.timer = None
self.Destroy()
class Print(InkstitchExtension):
@ -330,12 +466,11 @@ class Print(InkstitchExtension):
realistic_color_block_svgs=realistic_color_block_svgs
)
print_server.start()
# 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)
browser_window = open_url(print_server.host, print_server.port, True)
browser_window.wait()
print_server.stop()
print_server.join()
time.sleep(1)
open_url("http://%s:%s/" % (print_server.host, print_server.port))
app = wx.App()
info_frame = PrintInfoFrame(None, title=_("Ink/Stitch Print"), size=(450, 350), print_server=print_server)
info_frame.Show()
app.MainLoop()

Wyświetl plik

@ -11,6 +11,12 @@ var realistic_rendering = {};
var realistic_cache = {};
var normal_rendering = {};
function ping() {
$.get("/ping")
.done(function() { setTimeout(ping, 1000) })
.fail(function() { $('#errors').attr('class', 'show') });
}
//function to chunk opd view into pieces
// source: https://stackoverflow.com/questions/3366529/wrap-every-3-divs-in-a-div
$.fn.chunk = function(size) {
@ -203,6 +209,7 @@ function setSVGTransform(figure, transform) {
}
$(function() {
setTimeout(ping, 1000);
/* SCALING AND MOVING SVG */
/* Mousewheel scaling */
@ -372,20 +379,35 @@ $(function() {
/* Settings Bar */
$('button.close').click(function() {
window.close();
$.post('/shutdown', {})
.always(function(data) {
window.close();
/* Chrome and Firefox both have a rule: scripts can only close windows
* that they opened. Chrome seems to have an exception for windows that
* were opened by an outside program, so the above works fine. Firefox
* steadfastly refuses to allow us to close the window, so we'll tell
* the user (in their language) that they can close it.
*/
setTimeout(function() {
document.open();
document.write("<html><body>" + data + "</body></html>");
document.close();
}, 1000);
});
});
$('button.print').click(function() {
var pageSize = $('select#printing-size').find(':selected').text();
window.inkstitchAPI.openpdf(pageSize)
// printing halts all javascript activity, so we need to tell the backend
// not to shut down until we're done.
$.get("/printing/start")
.done(function() {
window.print();
$.get("/printing/end");
});
});
$('button.save-pdf').click(function() {
var pageSize = $('select#printing-size').find(':selected').text();
window.inkstitchAPI.savepdf(pageSize)
});
$('button.settings').click(function(){
$('button.settings').click(function(){
$('#settings-ui').show();
});

Wyświetl plik

@ -125,7 +125,7 @@ body {
color: white;
}
.ui button.print, .ui button.save-pdf {
.ui button.print {
border: 1px solid rgb(50,132,50);
}

Wyświetl plik

@ -2,7 +2,6 @@
<p class="header">{{ _('Ink/Stitch Print Preview') }}</p>
<div class="buttons">
<button class="print">{{ _('Print') }}</button>
<button class="save-pdf">{{ _('Save PDF') }}</button>
<button class="settings">{{ _('Settings') }}</button>
<button class="close">{{ _('Close') }}</button>
</div>