diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py index 1a97ff7ae3..5175fbe9b2 100644 --- a/src/calibre/gui2/main.py +++ b/src/calibre/gui2/main.py @@ -5,7 +5,9 @@ import sys, os, time, socket, traceback, re from functools import partial import apsw -from PyQt5.Qt import (QCoreApplication, QIcon, QObject, QTimer) +from PyQt5.Qt import ( + QCoreApplication, QIcon, QObject, QTimer, Qt, QSplashScreen, QBrush, + QColor, QPixmap) from calibre import prints, plugins, force_unicode from calibre.constants import (iswindows, __appname__, isosx, DEBUG, islinux, @@ -15,7 +17,6 @@ from calibre.gui2 import ( ORG_NAME, APP_UID, initialize_file_icon_provider, Application, choose_dir, error_dialog, question_dialog, gprefs, setup_gui_option_parser) from calibre.gui2.main_window import option_parser as _option_parser -from calibre.gui2.splash import SplashScreen from calibre.utils.config import prefs, dynamic if iswindows: @@ -157,6 +158,34 @@ class EventAccumulator(object): def __call__(self, ev): self.events.append(ev) +class SplashScreen(QSplashScreen): + + def __init__(self): + self.drawn_once = False + QSplashScreen.__init__(self, QPixmap(I('library.png'))) + + def drawContents(self, painter): + self.drawn_once = True + painter.setBackgroundMode(Qt.OpaqueMode) + painter.setBackground(QBrush(QColor(0xee, 0xee, 0xee))) + painter.setPen(Qt.black) + painter.setRenderHint(painter.TextAntialiasing, True) + painter.drawText(self.rect().adjusted(5, 5, -5, -5), Qt.AlignLeft, self.message()) + + def show_message(self, msg): + self.showMessage(msg) + self.wait_for_draw() + + def wait_for_draw(self): + # Without this the splash screen is not painted on linux and windows + self.drawn_once = False + st = time.time() + while not self.drawn_once and (time.time() - st < 0.1): + Application.instance().processEvents() + + def show(self): + QSplashScreen.show(self) + class GuiRunner(QObject): '''Make sure an event loop is running before starting the main work of initialization''' @@ -178,8 +207,9 @@ class GuiRunner(QObject): main = self.main = Main(self.opts, gui_debug=self.gui_debug) if self.splash_screen is not None: self.splash_screen.show_message(_('Initializing user interface...')) + self.splash_screen.finish(main) with gprefs: # Only write gui.json after initialization is complete - main.initialize(self.library_path, db, self.listener, self.actions, splash_screen=self.splash_screen) + main.initialize(self.library_path, db, self.listener, self.actions) self.splash_screen = None if DEBUG: prints('Started up in %.2f seconds'%(time.time() - @@ -197,22 +227,14 @@ class GuiRunner(QObject): add_filesystem_book(event) self.app.file_event_hook = add_filesystem_book - def hide_splash_screen(self): - if self.splash_screen is not None: - with self.app: # Disable quit on last window closed - self.splash_screen.hide() - self.splash_screen = None - def choose_dir(self, initial_dir): - self.hide_splash_screen() - return choose_dir(None, 'choose calibre library', + return choose_dir(self.splash_screen, 'choose calibre library', _('Choose a location for your new calibre e-book library'), default_dir=initial_dir) def show_error(self, title, msg, det_msg=''): - self.hide_splash_screen() with self.app: - error_dialog(self.main, title, msg, det_msg=det_msg, show=True) + error_dialog(self.splash_screen, title, msg, det_msg=det_msg, show=True) def initialization_failed(self): print 'Catastrophic failure initializing GUI, bailing out...' @@ -255,9 +277,8 @@ class GuiRunner(QObject): try: db = LibraryDatabase(self.library_path) except apsw.Error: - self.hide_splash_screen() with self.app: - repair = question_dialog(None, _('Corrupted database'), + repair = question_dialog(self.splash_screen, _('Corrupted database'), _('The library database at %s appears to be corrupted. Do ' 'you want calibre to try and rebuild it automatically? ' 'The rebuild may not be completely successful. ' @@ -277,13 +298,13 @@ class GuiRunner(QObject): self.initialize_db_stage2(db, None) def show_splash_screen(self): - self.splash_screen = SplashScreen(get_debug_executable()) + self.splash_screen = SplashScreen() + self.splash_screen.show() self.splash_screen.show_message(_('Starting %s: Loading books...') % __appname__) def initialize(self, *args): if gprefs['show_splash_screen']: self.show_splash_screen() - self.library_path = get_library_path(self) if not self.library_path: self.initialization_failed() diff --git a/src/calibre/gui2/splash.py b/src/calibre/gui2/splash.py deleted file mode 100644 index 785e7a0101..0000000000 --- a/src/calibre/gui2/splash.py +++ /dev/null @@ -1,165 +0,0 @@ -#!/usr/bin/env python2 -# vim:fileencoding=utf-8 -from __future__ import (unicode_literals, division, absolute_import, - print_function) - -__license__ = 'GPL v3' -__copyright__ = '2014, Kovid Goyal ' - -import os, subprocess, time, struct, sys, errno -from threading import Thread -from Queue import Queue -from functools import partial - -from PyQt5.Qt import QApplication, QSplashScreen, pyqtSignal, QBrush, QColor, Qt, QPixmap - -from calibre.constants import isosx, DEBUG -from calibre.utils.ipc import eintr_retry_call - -class SplashScreen(Thread): - - daemon = True - - def __init__(self, debug_executable): - Thread.__init__(self) - self.queue = Queue() - try: - self.launch_process(debug_executable) - except Exception: - import traceback - traceback.print_exc() - self.process = None - self.keep_going = True - if self.process is not None: - self.start() - self.show_message = partial(self._rpc, 'show_message') - self.hide = partial(self._rpc, 'hide') - - def launch_process(self, debug_executable): - from calibre.utils.ipc.simple_worker import start_pipe_worker - if DEBUG: - args = {'stdout':None, 'stderr': None} - else: - args = {'stdout':open(os.devnull, 'wb'), 'stderr':subprocess.STDOUT} - self.process = start_pipe_worker('from calibre.gui2.splash import main; main()', **args) - - def _rpc(self, name, *args): - self.queue.put(('_' + name, args)) - - def run(self): - while self.keep_going: - try: - func, args = self.queue.get() - if func == '_hide': - self.keep_going = False - getattr(self, func)(*args) - except Exception: - import traceback - traceback.print_exc() - self.terminate_worker() - - def terminate_worker(self): - if self.process is None: - return - self.process.stdin.close() - # Give the worker two seconds to exit naturally - c = 20 - while self.process.poll() is None and c > 0: - time.sleep(0.1) - c -= 1 - if self.process.poll() is None: - try: - self.process.terminate() - except EnvironmentError as e: - if getattr(e, 'errno', None) != errno.ESRCH: # ESRCH ==> process does not exist anymore - import traceback - traceback.print_exc() - self.process.wait() - - def send(self, msg): - if self.process is not None and not self.process.stdin.closed: - if not isinstance(msg, bytes): - msg = msg.encode('utf-8') - eintr_retry_call(self.process.stdin.write, struct.pack(b'>L', len(msg)) + msg) - - def _show_message(self, msg): - self.send(msg) - - def _hide(self): - if self.process is not None: - self.process.stdin.close() - -def read(amount): - ans = b'' - left = amount - while left > 0: - raw = eintr_retry_call(sys.stdin.read, left) - if len(raw) == 0: - raise EOFError('') - left -= len(raw) - ans += raw - return ans - -def run_loop(splash_screen): - shutdown = splash_screen.shutdown.emit - try: - while True: - raw = read(4) - mlen = struct.unpack(b'>L', raw)[0] - msg = read(mlen).decode('utf-8') - if not msg: - return shutdown() - splash_screen.show_message.emit(msg) - except EOFError: - pass - except: - import traceback - traceback.print_exc() - return shutdown() - -class CalibreSplashScreen(QSplashScreen): - - shutdown = pyqtSignal() - show_message = pyqtSignal(object) - - def __init__(self): - QSplashScreen.__init__(self, QPixmap(I('library.png'))) - - def drawContents(self, painter): - painter.setBackgroundMode(Qt.OpaqueMode) - painter.setBackground(QBrush(QColor(0xee, 0xee, 0xee))) - painter.setPen(Qt.black) - painter.setRenderHint(painter.TextAntialiasing, True) - painter.drawText(self.rect().adjusted(5, 5, -5, -5), Qt.AlignLeft, self.message()) - - -def main(): - os.closerange(3, 256) - app = QApplication([]) - s = CalibreSplashScreen() - s.show_message.connect(s.showMessage, type=Qt.QueuedConnection) - s.shutdown.connect(app.quit, type=Qt.QueuedConnection) - s.show() - t = Thread(target=run_loop, args=(s,)) - t.daemon = True - t.start() - app.exec_() - -if isosx: - # Showing the splash screen in a separate process doesn't work on OS X and - # I can't be bothered to figure out why - del SplashScreen - - class SplashScreen(CalibreSplashScreen): - - def __init__(self, *args): - CalibreSplashScreen.__init__(self) - self.show() - - def show(self): - CalibreSplashScreen.show(self) - QApplication.instance().processEvents() - QApplication.instance().flush() - - def show_message(self, msg): - CalibreSplashScreen.showMessage(self, msg) diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index dec58be06d..ac957cff55 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -209,7 +209,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ else: stmap[st.name] = st - def initialize(self, library_path, db, listener, actions, show_gui=True, splash_screen=None): + def initialize(self, library_path, db, listener, actions, show_gui=True): opts = self.opts self.preferences_action, self.quit_action = actions self.library_path = library_path @@ -335,8 +335,6 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ if show_gui: self.show() - if splash_screen is not None: - splash_screen.hide() if self.system_tray_icon is not None and self.system_tray_icon.isVisible() and opts.start_in_tray: self.hide_windows()