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