source: orange/Orange/OrangeCanvas/main.py @ 11847:2623f250950d

Revision 11847:2623f250950d, 11.4 KB checked in by Ales Erjavec <ales.erjavec@…>, 3 months ago (diff)

A temporary fix for QTBUG-32789 (wrong system font on OSX-Mavericks).

Line 
1"""
2Orange Canvas main entry point
3
4"""
5
6import os
7import sys
8import gc
9import re
10import logging
11import optparse
12import cPickle
13import shlex
14from contextlib import nested
15
16import pkg_resources
17
18from PyQt4.QtGui import QFont, QColor
19from PyQt4.QtCore import Qt, QDir
20
21from Orange import OrangeCanvas
22from Orange.OrangeCanvas.application.application import CanvasApplication
23from Orange.OrangeCanvas.application.canvasmain import CanvasMainWindow
24from Orange.OrangeCanvas.application.outputview import TextStream, ExceptHook
25
26from Orange.OrangeCanvas.gui.splashscreen import SplashScreen
27from Orange.OrangeCanvas.config import cache_dir
28from Orange.OrangeCanvas import config
29from Orange.OrangeCanvas.utils.redirect import redirect_stdout, redirect_stderr
30from Orange.OrangeCanvas.utils.qtcompat import QSettings
31
32from Orange.OrangeCanvas.registry import qt
33from Orange.OrangeCanvas.registry import WidgetRegistry, set_global_registry
34from Orange.OrangeCanvas.registry import cache
35
36log = logging.getLogger(__name__)
37
38
39def running_in_ipython():
40    try:
41        __IPYTHON__
42        return True
43    except NameError:
44        return False
45
46
47def fix_osx_10_9_private_font():
48    """Temporary fix for QTBUG-32789."""
49    from PyQt4.QtCore import QSysInfo
50    if sys.platform == "darwin":
51        try:
52            if QSysInfo.MacintoshVersion > QSysInfo.MV_10_8:
53                QFont.insertSubstitution(".Lucida Grande UI", "Lucida Grande")
54        except AttributeError:
55            pass
56
57
58def fix_win_pythonw_std_stream():
59    """
60    On windows when running without a console (using pythonw.exe) the
61    std[err|out] file descriptors are invalid and start throwing exceptions
62    when their buffer is flushed (`http://bugs.python.org/issue706263`_)
63
64    """
65    if sys.platform == "win32" and \
66            os.path.basename(sys.executable) == "pythonw.exe":
67        if sys.stdout.fileno() < 0:
68            sys.stdout = open(os.devnull, "wb")
69        if sys.stderr.fileno() < 0:
70            sys.stderr = open(os.devnull, "wb")
71
72
73def main(argv=None):
74    if argv is None:
75        argv = sys.argv
76
77    usage = "usage: %prog [options] [scheme_file]"
78    parser = optparse.OptionParser(usage=usage)
79
80    parser.add_option("--no-discovery",
81                      action="store_true",
82                      help="Don't run widget discovery "
83                           "(use full cache instead)")
84
85    parser.add_option("--force-discovery",
86                      action="store_true",
87                      help="Force full widget discovery "
88                           "(invalidate cache)")
89    parser.add_option("--no-welcome",
90                      action="store_true",
91                      help="Don't show welcome dialog.")
92    parser.add_option("--no-splash",
93                      action="store_true",
94                      help="Don't show splash screen.")
95    parser.add_option("-l", "--log-level",
96                      help="Logging level (0, 1, 2, 3, 4)",
97                      type="int", default=1)
98    parser.add_option("--no-redirect",
99                      action="store_true",
100                      help="Do not redirect stdout/err to canvas output view.")
101    parser.add_option("--style",
102                      help="QStyle to use",
103                      type="str", default=None)
104    parser.add_option("--stylesheet",
105                      help="Application level CSS style sheet to use",
106                      type="str", default="orange.qss")
107    parser.add_option("--qt",
108                      help="Additional arguments for QApplication",
109                      type="str", default=None)
110
111    (options, args) = parser.parse_args(argv[1:])
112
113    levels = [logging.CRITICAL,
114              logging.ERROR,
115              logging.WARN,
116              logging.INFO,
117              logging.DEBUG]
118
119    # Fix streams before configuring logging (otherwise it will store
120    # and write to the old file descriptors)
121    fix_win_pythonw_std_stream()
122
123    # Try to fix fonts on OSX Mavericks
124    fix_osx_10_9_private_font()
125
126    # File handler should always be at least INFO level so we need
127    # the application root level to be at least at INFO.
128    root_level = min(levels[options.log_level], logging.INFO)
129    rootlogger = logging.getLogger(OrangeCanvas.__name__)
130    rootlogger.setLevel(root_level)
131
132    # Standard output stream handler at the requested level
133    stream_hander = logging.StreamHandler()
134    stream_hander.setLevel(level=levels[options.log_level])
135    rootlogger.addHandler(stream_hander)
136
137    log.info("Starting 'Orange Canvas' application.")
138
139    qt_argv = argv[:1]
140
141    if options.style is not None:
142        qt_argv += ["-style", options.style]
143
144    if options.qt is not None:
145        qt_argv += shlex.split(options.qt)
146
147    qt_argv += args
148
149    log.debug("Starting CanvasApplicaiton with argv = %r.", qt_argv)
150    app = CanvasApplication(qt_argv)
151
152    # NOTE: config.init() must be called after the QApplication constructor
153    config.init()
154
155    file_handler = logging.FileHandler(
156        filename=os.path.join(config.log_dir(), "canvas.log"),
157        mode="w"
158    )
159
160    file_handler.setLevel(root_level)
161    rootlogger.addHandler(file_handler)
162
163    # intercept any QFileOpenEvent requests until the main window is
164    # fully initialized.
165    # NOTE: The QApplication must have the executable ($0) and filename
166    # arguments passed in argv otherwise the FileOpen events are
167    # triggered for them (this is done by Cocoa, but QApplicaiton filters
168    # them out if passed in argv)
169
170    open_requests = []
171
172    def onrequest(url):
173        log.info("Received an file open request %s", url)
174        open_requests.append(url)
175
176    app.fileOpenRequest.connect(onrequest)
177
178    settings = QSettings()
179
180    stylesheet = options.stylesheet
181    stylesheet_string = None
182
183    if stylesheet != "none":
184        if os.path.isfile(stylesheet):
185            stylesheet_string = open(stylesheet, "rb").read()
186        else:
187            if not os.path.splitext(stylesheet)[1]:
188                # no extension
189                stylesheet = os.path.extsep.join([stylesheet, "qss"])
190
191            pkg_name = OrangeCanvas.__name__
192            resource = "styles/" + stylesheet
193
194            if pkg_resources.resource_exists(pkg_name, resource):
195                stylesheet_string = \
196                    pkg_resources.resource_string(pkg_name, resource)
197
198                base = pkg_resources.resource_filename(pkg_name, "styles")
199
200                pattern = re.compile(
201                    r"^\s@([a-zA-Z0-9_]+?)\s*:\s*([a-zA-Z0-9_/]+?);\s*$",
202                    flags=re.MULTILINE
203                )
204
205                matches = pattern.findall(stylesheet_string)
206
207                for prefix, search_path in matches:
208                    QDir.addSearchPath(prefix, os.path.join(base, search_path))
209                    log.info("Adding search path %r for prefix, %r",
210                             search_path, prefix)
211
212                stylesheet_string = pattern.sub("", stylesheet_string)
213
214            else:
215                log.info("%r style sheet not found.", stylesheet)
216
217    # Add the default canvas_icons search path
218    dirpath = os.path.abspath(os.path.dirname(OrangeCanvas.__file__))
219    QDir.addSearchPath("canvas_icons", os.path.join(dirpath, "icons"))
220
221    canvas_window = CanvasMainWindow()
222
223    if stylesheet_string is not None:
224        canvas_window.setStyleSheet(stylesheet_string)
225
226    if not options.force_discovery:
227        reg_cache = cache.registry_cache()
228    else:
229        reg_cache = None
230
231    widget_discovery = qt.QtWidgetDiscovery(cached_descriptions=reg_cache)
232
233    widget_registry = qt.QtWidgetRegistry()
234
235    widget_discovery.found_category.connect(
236        widget_registry.register_category
237    )
238    widget_discovery.found_widget.connect(
239        widget_registry.register_widget
240    )
241
242    want_splash = \
243        settings.value("startup/show-splash-screen", True, type=bool) and \
244        not options.no_splash
245
246    if want_splash:
247        pm, rect = config.splash_screen()
248        splash_screen = SplashScreen(pixmap=pm, textRect=rect)
249        splash_screen.setFont(QFont("Helvetica", 12))
250        color = QColor("#FFD39F")
251
252        def show_message(message):
253            splash_screen.showMessage(message, color=color)
254
255        widget_discovery.discovery_start.connect(splash_screen.show)
256        widget_discovery.discovery_process.connect(show_message)
257        widget_discovery.discovery_finished.connect(splash_screen.hide)
258
259    log.info("Running widget discovery process.")
260
261    cache_filename = os.path.join(cache_dir(), "widget-registry.pck")
262    if options.no_discovery:
263        widget_registry = cPickle.load(open(cache_filename, "rb"))
264        widget_registry = qt.QtWidgetRegistry(widget_registry)
265    else:
266        widget_discovery.run(config.widgets_entry_points())
267        # Store cached descriptions
268        cache.save_registry_cache(widget_discovery.cached_descriptions)
269        cPickle.dump(WidgetRegistry(widget_registry),
270                     open(cache_filename, "wb"))
271    set_global_registry(widget_registry)
272    canvas_window.set_widget_registry(widget_registry)
273    canvas_window.show()
274    canvas_window.raise_()
275
276    want_welcome = \
277        settings.value("startup/show-welcome-screen", True, type=bool) \
278        and not options.no_welcome
279
280    # Process events to make sure the canvas_window layout has
281    # a chance to activate (the welcome dialog is modal and will
282    # block the event queue, plus we need a chance to receive open file
283    # signals when running without a splash screen)
284    app.processEvents()
285
286    app.fileOpenRequest.connect(canvas_window.open_scheme_file)
287
288    if want_welcome and not args and not open_requests:
289        canvas_window.welcome_dialog()
290
291    elif args:
292        log.info("Loading a scheme from the command line argument %r",
293                 args[0])
294        canvas_window.load_scheme(args[0])
295    elif open_requests:
296        log.info("Loading a scheme from an `QFileOpenEvent` for %r",
297                 open_requests[-1])
298        canvas_window.load_scheme(open_requests[-1].toLocalFile())
299
300    stdout_redirect = \
301        settings.value("output/redirect-stdout", True, type=bool)
302
303    stderr_redirect = \
304        settings.value("output/redirect-stderr", True, type=bool)
305
306    # cmd line option overrides settings / no redirect is possible
307    # under ipython
308    if options.no_redirect or running_in_ipython():
309        stderr_redirect = stdout_redirect = False
310
311    output_view = canvas_window.output_view()
312
313    if stdout_redirect:
314        stdout = TextStream()
315        stdout.stream.connect(output_view.write)
316        # also connect to original fd
317        stdout.stream.connect(sys.stdout.write)
318    else:
319        stdout = sys.stdout
320
321    if stderr_redirect:
322        error_writer = output_view.formated(color=Qt.red)
323        stderr = TextStream()
324        stderr.stream.connect(error_writer.write)
325        # also connect to original fd
326        stderr.stream.connect(sys.stderr.write)
327    else:
328        stderr = sys.stderr
329
330    if stderr_redirect:
331        sys.excepthook = ExceptHook()
332        sys.excepthook.handledException.connect(output_view.parent().show)
333
334    with nested(redirect_stdout(stdout), redirect_stderr(stderr)):
335        log.info("Entering main event loop.")
336        try:
337            status = app.exec_()
338        except BaseException:
339            log.error("Error in main event loop.", exc_info=True)
340
341    canvas_window.deleteLater()
342    app.processEvents()
343    app.flush()
344    del canvas_window
345
346    # Collect any cycles before deleting the QApplication instance
347    gc.collect()
348
349    del app
350    return status
351
352
353if __name__ == "__main__":
354    sys.exit(main())
Note: See TracBrowser for help on using the repository browser.