source: orange/Orange/OrangeCanvas/main.py @ 11535:6805b5990d25

Revision 11535:6805b5990d25, 10.2 KB checked in by Ales Erjavec <ales.erjavec@…>, 11 months ago (diff)

Pass the full executable path and positional filename arguments to QApplication.

Fixes a problem when starting in OSX.

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