forked from robotframework/RIDE
-
Notifications
You must be signed in to change notification settings - Fork 29
/
Copy pathapplication.py
473 lines (428 loc) · 20.8 KB
/
application.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
# Copyright 2008-2015 Nokia Networks
# Copyright 2016- Robot Framework Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import builtins
import os
from csv import excel
import wx
from contextlib import contextmanager
from pathlib import Path
from ..namespace import Namespace
from ..controller import Project
from ..spec import librarydatabase
from ..ui import LoadProgressObserver
from ..ui.mainframe import RideFrame
from .. import publish
from .. import context, contrib
# from ..context import coreplugins
from ..preferences import Preferences, RideSettings
from ..application.pluginloader import PluginLoader
from ..application.editorprovider import EditorProvider
from ..application.releasenotes import ReleaseNotes
from ..application.updatenotifier import UpdateNotifierController, UpdateDialog
from ..ui.mainframe import ToolBar
from ..ui.fileexplorerplugin import FileExplorerPlugin
from ..utils import RideFSWatcherHandler, run_python_command
from ..lib.robot.utils.encodingsniffer import get_system_encoding
from ..publish import PUBLISHER
from ..publish.messages import RideSettingsChanged
from ..widgets.button import ButtonWithHandler
from wx.lib.agw.aui import AuiDefaultToolBarArt
from wx.lib.agw.aui.auibar import AuiToolBar
from wx.lib.agw.aui.auibook import AuiTabCtrl, TabFrame
try:
from robot.conf import languages
except ImportError:
languages = None
# add translation macro to builtin similar to what gettext does
# generated pot with: /usr/bin/python /usr/bin/pygettext.py -a -d RIDE -o RIDE.pot -p ./localization ../robotide
_ = wx.GetTranslation # To keep linter/code analyser happy
builtins.__dict__['_'] = wx.GetTranslation
BACKGROUND_HELP = 'background help'
FOREGROUND_TEXT = 'foreground text'
FONT_SIZE = 'font size'
FONT_FACE = 'font face'
class UnthemableWidgetError(Exception):
def __init__(self):
Exception.__init__(self, 'HELP! I have no clue how to theme this.')
class RIDE(wx.App):
_controller = None
_editor_provider = None
_locale = None
_plugin_loader = None
editor = None
fileexplorerplugin = None
fontinfo = None
frame = None
namespace = None
preferences = None
robot_version = None
settings = None
treeplugin = None
def __init__(self, path=None, updatecheck=True, settingspath=None):
self._updatecheck = updatecheck
self.workspace_path = path
self.settings_path = settingspath
context.APP = self
wx.App.__init__(self, redirect=False)
def OnInit(self): # Overrides wx method
# DEBUG To test RTL
# self._initial_locale = wx.Locale(wx.LANGUAGE_ARABIC)
self._locale = wx.Locale(wx.LANGUAGE_ENGLISH_US) # LANGUAGE_PORTUGUESE
# Needed for SetToolTipString to work
wx.HelpProvider.Set(wx.SimpleHelpProvider()) # DEBUG: adjust to wx versions
self.settings = RideSettings(self.settings_path)
class Message:
keys = ['General']
self.change_locale(Message) # This was done here to have menus translated, but not working
# print(f"DEBUG: application.py RIDE OnInit after changing localization {self._locale.GetCanonicalName()=}")
# Importing libraries after setting language
from ..context import coreplugins, SETTINGS_DIRECTORY
from ..ui.treeplugin import TreePlugin
librarydatabase.initialize_database()
self.preferences = Preferences(self.settings)
self.namespace = Namespace(self.settings)
self._controller = Project(self.namespace, self.settings)
# Try to get FontInfo as soon as possible
font_size = self.settings['General'].get('font size', 12)
font_face = self.settings['General'].get('font face', 'Helvetica')
self.fontinfo = wx.FontInfo(font_size).FaceName(font_face).Bold(False)
self.frame = RideFrame(self, self._controller)
# DEBUG self.frame.Show()
self._editor_provider = EditorProvider()
self._plugin_loader = PluginLoader(self, self._get_plugin_dirs(),
coreplugins.get_core_plugins())
self._plugin_loader.enable_plugins()
perspective = self.settings.get('AUI Perspective', None)
if perspective:
self.frame.aui_mgr.LoadPerspective(perspective, True)
try:
nb_perspective = self.settings.get('AUI NB Perspective', None)
if nb_perspective:
self.frame.notebook.LoadPerspective(nb_perspective)
except Exception as e:
print(f"RIDE: There was a problem loading panels position."
f" Please delete the definition 'AUI NB Perspective' in "
f"{os.path.join(SETTINGS_DIRECTORY, 'settings.cfg')}")
if not isinstance(e, IndexError): # If is with all notebooks disabled, continue
raise e
self.fileexplorerplugin = FileExplorerPlugin(self, self._controller)
self.treeplugin = TreePlugin(self)
if self.treeplugin.settings['_enabled']:
self.treeplugin.register_frame(self.frame)
if not self.treeplugin.opened:
self.treeplugin.close_tree()
self.editor = self._get_editor()
self.robot_version = self._find_robot_installation()
self._load_data()
self.treeplugin.populate(self.model)
self.treeplugin.set_editor(self.editor)
self._publish_system_info()
self.frame.Show() # ###### DEBUG DANGER ZONE
self.SetTopWindow(self.frame)
self.frame.aui_mgr.Update()
if self._updatecheck:
wx.CallAfter(UpdateNotifierController(self.settings, self.frame.notebook).notify_update_if_needed,
UpdateDialog)
self.Bind(wx.EVT_ACTIVATE_APP, self.on_app_activate)
PUBLISHER.subscribe(self.SetGlobalColour, RideSettingsChanged)
PUBLISHER.subscribe(self.update_excludes, RideSettingsChanged)
RideSettingsChanged(keys=('Excludes', 'init'), old=None, new=None).publish()
PUBLISHER.subscribe(self.change_locale, RideSettingsChanged)
RideSettingsChanged(keys=('General', 'ui language'), old=None, new=None).publish()
wx.CallLater(600, ReleaseNotes(self).bring_to_front)
return True
def OnExit(self):
PUBLISHER.unsubscribe_all()
self.Destroy()
wx.Exit()
return True
@staticmethod
def _ApplyThemeToWidget(widget, fore_color=wx.BLUE, back_color=wx.LIGHT_GREY, theme: (None, dict) = None):
if theme is None:
theme = {'background': back_color, 'foreground': fore_color, 'secondary background': back_color,
'secondary foreground': fore_color}
background = theme['background']
foreground = theme['foreground']
secondary_background = theme['secondary background']
secondary_foreground = theme['secondary foreground']
background_help = theme[BACKGROUND_HELP]
foreground_text = theme[FOREGROUND_TEXT]
if isinstance(widget, AuiToolBar) or isinstance(widget, ToolBar):
aui_default_tool_bar_art = AuiDefaultToolBarArt()
aui_default_tool_bar_art.SetDefaultColours(wx.GREEN)
widget.SetBackgroundColour(background)
# widget.SetOwnBackgroundColour(background)
widget.SetForegroundColour(foreground)
# widget.SetOwnForegroundColour(foreground)
"""
widget.SetBackgroundColour(Colour(200, 222, 40))
widget.SetOwnBackgroundColour(Colour(200, 222, 40))
widget.SetForegroundColour(Colour(7, 0, 70))
widget.SetOwnForegroundColour(Colour(7, 0, 70))
"""
# or
elif isinstance(widget, wx.Control):
if not isinstance(widget, (wx.Button, wx.BitmapButton, ButtonWithHandler)):
widget.SetForegroundColour(foreground)
widget.SetBackgroundColour(background)
# widget.SetOwnBackgroundColour(background)
# widget.SetOwnForegroundColour(foreground)
else:
widget.SetForegroundColour(secondary_foreground)
widget.SetBackgroundColour(secondary_background)
# widget.SetOwnBackgroundColour(secondary_background)
# widget.SetOwnForegroundColour(secondary_foreground)
elif isinstance(widget, (wx.TextCtrl, TabFrame, AuiTabCtrl)):
widget.SetForegroundColour(foreground_text) # or fore_color
widget.SetBackgroundColour(background_help) # or back_color
elif isinstance(widget, (RideFrame, wx.Panel)):
widget.SetForegroundColour(foreground) # or fore_color
widget.SetBackgroundColour(background) # or fore_color
elif isinstance(widget, wx.MenuItem):
widget.SetTextColour(foreground)
widget.SetBackgroundColour(background)
# print(f"DEBUG: Application ApplyTheme wx.MenuItem {type(widget)}")
else:
widget.SetBackgroundColour(background)
# widget.SetOwnBackgroundColour(background)
widget.SetForegroundColour(foreground)
# widget.SetOwnForegroundColour(foreground)
def _WalkWidgets(self, widget, indent=0, indent_level=4, theme=None):
# print(' ' * indent + widget.__class__.__name__)
if theme is None:
theme = {}
widget.Freeze()
# print(f"DEBUG Application General : _WalkWidgets background {theme['background']}")
self._ApplyThemeToWidget(widget=widget, theme=theme)
for child in widget.GetChildren():
if not child.IsTopLevel(): # or isinstance(child, wx.PopupWindow)):
indent += indent_level
self._WalkWidgets(child, indent, indent_level, theme)
indent -= indent_level
widget.Thaw()
def SetGlobalColour(self, message):
if message.keys[0] != "General":
return
# print(f"DEBUG Application General : Enter SetGlobalColour message= {message.keys[0]}")
app = wx.App.Get()
_root = app.GetTopWindow()
theme = self.settings.get_without_default('General')
font_size = theme[FONT_SIZE]
font_face = theme[FONT_FACE]
font = _root.GetFont()
font.SetFaceName(font_face)
font.SetPointSize(font_size)
_root.SetFont(font)
self._WalkWidgets(_root, theme=theme)
# print(f"DEBUG Application General : SetGlobalColour AppliedWidgets check Filexplorer and Tree")
if theme['apply to panels'] and self.fileexplorerplugin.settings['_enabled']:
self.fileexplorerplugin.settings['background'] = theme['background']
self.fileexplorerplugin.settings['foreground'] = theme['foreground']
self.fileexplorerplugin.settings[FOREGROUND_TEXT] = theme[FOREGROUND_TEXT]
self.fileexplorerplugin.settings[BACKGROUND_HELP] = theme[BACKGROUND_HELP]
self.fileexplorerplugin.settings[FONT_SIZE] = theme[FONT_SIZE]
self.fileexplorerplugin.settings[FONT_FACE] = theme[FONT_FACE]
if self.fileexplorerplugin.settings['opened']:
self.fileexplorerplugin.show_file_explorer()
if theme['apply to panels'] and self.treeplugin.settings['_enabled']:
self.treeplugin.settings['background'] = theme['background']
self.treeplugin.settings['foreground'] = theme['foreground']
self.treeplugin.settings[FOREGROUND_TEXT] = theme[FOREGROUND_TEXT]
self.treeplugin.settings[BACKGROUND_HELP] = theme[BACKGROUND_HELP]
self.treeplugin.settings[FONT_SIZE] = theme[FONT_SIZE]
self.treeplugin.settings[FONT_FACE] = theme[FONT_FACE]
if self.treeplugin.settings['opened']:
self.treeplugin.on_show_tree(None)
"""
all_windows = list()
general = self.settings.get('General', None)
# print(f"DEBUG: Application General {general['background']} Type message {type(message)}")
# print(f"DEBUG: Application General message keys {message.keys} old {message.old} new {message.new}")
background = general['background']
foreground = general['foreground']
background_help = general[BACKGROUND_HELP]
foreground_text = general[FOREGROUND_TEXT]
font_size = general[FONT_SIZE]
font_face = general[FONT_FACE]
font = _root.GetFont()
font.SetFaceName(font_face)
font.SetPointSize(font_size)
_root.SetFont(font)
def _iterate_all_windows(root):
if hasattr(root, 'GetChildren'):
children = root.GetChildren()
if children:
for c in children:
_iterate_all_windows(c)
all_windows.append(root)
_iterate_all_windows(_root)
for w in all_windows:
if hasattr(w, 'SetHTMLBackgroundColour'):
w.SetHTMLBackgroundColour(wx.Colour(background_help))
w.SetForegroundColour(wx.Colour(foreground_text)) # 7, 0, 70))
elif hasattr(w, 'SetBackgroundColour'):
w.SetBackgroundColour(wx.Colour(background)) # 44, 134, 179))
# if hasattr(w, 'SetOwnBackgroundColour'):
# w.SetOwnBackgroundColour(wx.Colour(background)) # 44, 134, 179))
if hasattr(w, 'SetForegroundColour'):
w.SetForegroundColour(wx.Colour(foreground)) # 7, 0, 70))
# if hasattr(w, 'SetOwnForegroundColour'):
# w.SetOwnForegroundColour(wx.Colour(foreground)) # 7, 0, 70))
if hasattr(w, 'SetFont'):
w.SetFont(font)
"""
def change_locale(self, message):
if message.keys[0] != "General":
return
initial_locale = self._locale.GetName()
if languages:
from ..preferences import Languages
names = [n for n in Languages.names]
else:
names = [('English', 'en', wx.LANGUAGE_ENGLISH)]
general = self.settings.get_without_default('General')
language = general.get('ui language', 'English')
try:
idx = [lang[0] for lang in names].index(language)
code = names[idx][2]
except (IndexError, ValueError):
print(f"DEBUG: application.py RIDE change_locale ERROR: Could not find {language=}")
code = wx.LANGUAGE_ENGLISH_WORLD
del self._locale
self._locale = wx.Locale(code)
if not self._locale.IsOk():
self._locale = wx.Locale(wx.LANGUAGE_ENGLISH_WORLD)
lpath = Path(__file__).parent.absolute()
lpath = str(Path(Path.joinpath(lpath.parent, 'localization')).absolute())
wx.Locale.AddCatalogLookupPathPrefix(lpath)
self._locale.AddCatalog('RIDE')
if len(message.keys) > 1: # Avoid initial setting
from multiprocessing import shared_memory
from .restartutil import do_restart
new_locale = self._locale.GetName()
# print(f"DEBUG: application.py RIDE change_locale from {initial_locale} to {new_locale}")
if initial_locale != new_locale:
#if restart_dialog(): # DEBUG: See the in implementation why we don't restart
# print("DEBUG: application.py RIDE change_locale Restart accepted.")
# Shared memory to store language definition
try:
sharemem = shared_memory.ShareableList(['en'], name="language")
except FileExistsError: # Other instance created file
sharemem = shared_memory.ShareableList(name="language")
result = do_restart()
if result:
try:
sharemem.shm.close()
sharemem.shm.unlink()
except FileNotFoundError:
pass
@staticmethod
def update_excludes(message):
if message.keys[0] != "Excludes":
return
from ..preferences.excludes_class import Excludes
from ..context import SETTINGS_DIRECTORY
excludes = Excludes(SETTINGS_DIRECTORY)
paths = excludes.get_excludes().split()
if paths:
RideFSWatcherHandler.exclude_listening(paths)
@staticmethod
def _publish_system_info():
from ..context import SYSTEM_INFO
publish.RideLogMessage(SYSTEM_INFO).publish()
@property
def model(self):
return self._controller
def _get_plugin_dirs(self):
return [self.settings.get_path('plugins'),
os.path.join(self.settings['install root'], 'site-plugins'),
contrib.CONTRIB_PATH]
def _get_editor(self):
from ..editor import EditorPlugin
from ..editor.texteditor import TextEditorPlugin
for pl in self._plugin_loader.plugins:
maybe_editor = pl.conn_plugin
if (isinstance(maybe_editor, EditorPlugin) or isinstance(maybe_editor, TextEditorPlugin)) and\
maybe_editor.__getattr__("_enabled"):
return maybe_editor
def _load_data(self):
self.workspace_path = self.workspace_path or self._get_latest_path()
if self.workspace_path:
self._controller.update_default_dir(self.workspace_path)
observer = LoadProgressObserver(self.frame)
self._controller.load_data(self.workspace_path, observer)
@staticmethod
def _find_robot_installation():
output = run_python_command(
['import robot; print(robot.__file__ + \", \" + robot.__version__)'])
robot_found = b"ModuleNotFoundError" not in output and output
if robot_found:
system_encoding = get_system_encoding()
rf_file, rf_version = output.strip().split(b", ")
publish.RideLogMessage(_("Found Robot Framework version %s from %s.") % (
str(rf_version, system_encoding), str(os.path.dirname(rf_file), system_encoding))).publish()
return rf_version
else:
publish.RideLogMessage(publish.get_html_message('no_robot'), notify_user=True).publish()
def _get_latest_path(self):
recent = self._get_recentfiles_plugin()
if not recent or not recent.recent_files:
return None
return recent.recent_files[0]
def _get_recentfiles_plugin(self):
from ..recentfiles import RecentFilesPlugin
for pl in self.get_plugins():
if isinstance(pl.conn_plugin, RecentFilesPlugin):
return pl.conn_plugin
def get_plugins(self):
return self._plugin_loader.plugins
def register_preference_panel(self, panel_class):
"""Add the given panel class to the list of known preference panels"""
self.preferences.add(panel_class)
def unregister_preference_panel(self, panel_class):
"""Remove the given panel class from the known preference panels"""
self.preferences.remove(panel_class)
def register_editor(self, object_class, editor_class, activate):
self._editor_provider.register_editor(object_class, editor_class,
activate)
def unregister_editor(self, object_class, editor_class):
self._editor_provider.unregister_editor(object_class, editor_class)
def activate_editor(self, object_class, editor_class):
self._editor_provider.set_active_editor(object_class, editor_class)
def get_editors(self, object_class):
return self._editor_provider.get_editors(object_class)
def get_editor(self, object_class):
return self._editor_provider.get_editor(object_class)
@contextmanager
def active_event_loop(self):
# With wxPython 2.9.1, ProgressBar.Pulse breaks if there's no active
# event loop.
# See http://code.google.com/p/robotframework-ride/issues/detail?id=798
loop = wx.EventLoop()
wx.EventLoop.SetActive(loop)
yield
del loop
def OnEventLoopEnter(self, loop): # Overrides wx method
if loop and wx.EventLoopBase.IsMain(loop):
RideFSWatcherHandler.create_fs_watcher(self.workspace_path)
def on_app_activate(self, event):
if self.workspace_path is not None and RideFSWatcherHandler.is_watcher_created():
if event.GetActive():
if RideFSWatcherHandler.is_workspace_dirty():
self.frame.show_confirm_reload_dlg(event)
RideFSWatcherHandler.stop_listening()
else:
RideFSWatcherHandler.start_listening(self.workspace_path)
event.Skip()