source: orange/Orange/OrangeCanvas/main.py @ 11702:fc917b3c9a15

Revision 11702:fc917b3c9a15, 11.0 KB checked in by Ales Erjavec <ales.erjavec@…>, 7 months ago (diff)

Added logging to a file.

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    # File handler should always be at least INFO level so we need
113    # the application root level to be at least at INFO.
114    root_level = min(levels[options.log_level], logging.INFO)
115    rootlogger = logging.getLogger(OrangeCanvas.__name__)
116    rootlogger.setLevel(root_level)
117
118    # Standard output stream handler at the requested level
119    stream_hander = logging.StreamHandler()
120    stream_hander.setLevel(level=levels[options.log_level])
121    rootlogger.addHandler(stream_hander)
122
123    log.info("Starting 'Orange Canvas' application.")
124
125    qt_argv = argv[:1]
126
127    if options.style is not None:
128        qt_argv += ["-style", options.style]
129
130    if options.qt is not None:
131        qt_argv += shlex.split(options.qt)
132
133    qt_argv += args
134
135    log.debug("Starting CanvasApplicaiton with argv = %r.", qt_argv)
136    app = CanvasApplication(qt_argv)
137
138    # NOTE: config.init() must be called after the QApplication constructor
139    config.init()
140
141    file_handler = logging.FileHandler(
142        filename=os.path.join(config.log_dir(), "canvas.log"),
143        mode="w"
144    )
145
146    file_handler.setLevel(root_level)
147    rootlogger.addHandler(file_handler)
148
149    # intercept any QFileOpenEvent requests until the main window is
150    # fully initialized.
151    # NOTE: The QApplication must have the executable ($0) and filename
152    # arguments passed in argv otherwise the FileOpen events are
153    # triggered for them (this is done by Cocoa, but QApplicaiton filters
154    # them out if passed in argv)
155
156    open_requests = []
157
158    def onrequest(url):
159        log.info("Received an file open request %s", url)
160        open_requests.append(url)
161
162    app.fileOpenRequest.connect(onrequest)
163
164    settings = QSettings()
165
166    stylesheet = options.stylesheet
167    stylesheet_string = None
168
169    if stylesheet != "none":
170        if os.path.isfile(stylesheet):
171            stylesheet_string = open(stylesheet, "rb").read()
172        else:
173            if not os.path.splitext(stylesheet)[1]:
174                # no extension
175                stylesheet = os.path.extsep.join([stylesheet, "qss"])
176
177            pkg_name = OrangeCanvas.__name__
178            resource = "styles/" + stylesheet
179
180            if pkg_resources.resource_exists(pkg_name, resource):
181                stylesheet_string = \
182                    pkg_resources.resource_string(pkg_name, resource)
183
184                base = pkg_resources.resource_filename(pkg_name, "styles")
185
186                pattern = re.compile(
187                    r"^\s@([a-zA-Z0-9_]+?)\s*:\s*([a-zA-Z0-9_/]+?);\s*$",
188                    flags=re.MULTILINE
189                )
190
191                matches = pattern.findall(stylesheet_string)
192
193                for prefix, search_path in matches:
194                    QDir.addSearchPath(prefix, os.path.join(base, search_path))
195                    log.info("Adding search path %r for prefix, %r",
196                             search_path, prefix)
197
198                stylesheet_string = pattern.sub("", stylesheet_string)
199
200            else:
201                log.info("%r style sheet not found.", stylesheet)
202
203    # Add the default canvas_icons search path
204    dirpath = os.path.abspath(os.path.dirname(OrangeCanvas.__file__))
205    QDir.addSearchPath("canvas_icons", os.path.join(dirpath, "icons"))
206
207    canvas_window = CanvasMainWindow()
208
209    if stylesheet_string is not None:
210        canvas_window.setStyleSheet(stylesheet_string)
211
212    if not options.force_discovery:
213        reg_cache = cache.registry_cache()
214    else:
215        reg_cache = None
216
217    widget_discovery = qt.QtWidgetDiscovery(cached_descriptions=reg_cache)
218
219    widget_registry = qt.QtWidgetRegistry()
220
221    widget_discovery.found_category.connect(
222        widget_registry.register_category
223    )
224    widget_discovery.found_widget.connect(
225        widget_registry.register_widget
226    )
227
228    want_splash = \
229        settings.value("startup/show-splash-screen", True, type=bool) and \
230        not options.no_splash
231
232    if want_splash:
233        pm, rect = config.splash_screen()
234        splash_screen = SplashScreen(pixmap=pm, textRect=rect)
235        splash_screen.setFont(QFont("Helvetica", 12))
236        color = QColor("#FFD39F")
237
238        def show_message(message):
239            splash_screen.showMessage(message, color=color)
240
241        widget_discovery.discovery_start.connect(splash_screen.show)
242        widget_discovery.discovery_process.connect(show_message)
243        widget_discovery.discovery_finished.connect(splash_screen.hide)
244
245    log.info("Running widget discovery process.")
246
247    cache_filename = os.path.join(cache_dir(), "widget-registry.pck")
248    if options.no_discovery:
249        widget_registry = cPickle.load(open(cache_filename, "rb"))
250        widget_registry = qt.QtWidgetRegistry(widget_registry)
251    else:
252        widget_discovery.run(config.widgets_entry_points())
253        # Store cached descriptions
254        cache.save_registry_cache(widget_discovery.cached_descriptions)
255        cPickle.dump(WidgetRegistry(widget_registry),
256                     open(cache_filename, "wb"))
257    set_global_registry(widget_registry)
258    canvas_window.set_widget_registry(widget_registry)
259    canvas_window.show()
260    canvas_window.raise_()
261
262    want_welcome = \
263        settings.value("startup/show-welcome-screen", True, type=bool) \
264        and not options.no_welcome
265
266    # Process events to make sure the canvas_window layout has
267    # a chance to activate (the welcome dialog is modal and will
268    # block the event queue, plus we need a chance to receive open file
269    # signals when running without a splash screen)
270    app.processEvents()
271
272    app.fileOpenRequest.connect(canvas_window.open_scheme_file)
273
274    if want_welcome and not args and not open_requests:
275        canvas_window.welcome_dialog()
276
277    elif args:
278        log.info("Loading a scheme from the command line argument %r",
279                 args[0])
280        canvas_window.load_scheme(args[0])
281    elif open_requests:
282        log.info("Loading a scheme from an `QFileOpenEvent` for %r",
283                 open_requests[-1])
284        canvas_window.load_scheme(open_requests[-1].toLocalFile())
285
286    stdout_redirect = \
287        settings.value("output/redirect-stdout", True, type=bool)
288
289    stderr_redirect = \
290        settings.value("output/redirect-stderr", True, type=bool)
291
292    # cmd line option overrides settings / no redirect is possible
293    # under ipython
294    if options.no_redirect or running_in_ipython():
295        stderr_redirect = stdout_redirect = False
296
297    output_view = canvas_window.output_view()
298
299    if stdout_redirect:
300        stdout = TextStream()
301        stdout.stream.connect(output_view.write)
302        # also connect to original fd
303        stdout.stream.connect(sys.stdout.write)
304    else:
305        stdout = sys.stdout
306
307    if stderr_redirect:
308        error_writer = output_view.formated(color=Qt.red)
309        stderr = TextStream()
310        stderr.stream.connect(error_writer.write)
311        # also connect to original fd
312        stderr.stream.connect(sys.stderr.write)
313    else:
314        stderr = sys.stderr
315
316    if stderr_redirect:
317        sys.excepthook = ExceptHook()
318        sys.excepthook.handledException.connect(output_view.parent().show)
319
320    with nested(redirect_stdout(stdout), redirect_stderr(stderr)):
321        log.info("Entering main event loop.")
322        try:
323            status = app.exec_()
324        except BaseException:
325            log.error("Error in main event loop.", exc_info=True)
326
327    canvas_window.deleteLater()
328    app.processEvents()
329    app.flush()
330    del canvas_window
331
332    # Collect any cycles before deleting the QApplication instance
333    gc.collect()
334
335    del app
336    return status
337
338
339if __name__ == "__main__":
340    sys.exit(main())
Note: See TracBrowser for help on using the repository browser.