source: orange/Orange/OrangeCanvas/main.py @ 11339:5a36b4786b06

Revision 11339:5a36b4786b06, 9.6 KB checked in by Ales Erjavec <ales.erjavec@…>, 14 months ago (diff)

Fix invalid stdout/stderr streams on windows when running without a console.

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