source: orange/Orange/OrangeCanvas/main.py @ 11676:ea77c7c817b4

Revision 11676:ea77c7c817b4, 10.4 KB checked in by Ales Erjavec <ales.erjavec@…>, 8 months ago (diff)

Fix std streams (if necessary) before configuring the logging framework.

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