Coverage for src/robotide/ui/mainframe.py: 52%

646 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-05-06 10:40 +0100

1# Copyright 2008-2015 Nokia Networks 

2# Copyright 2016- Robot Framework Foundation 

3# 

4# Licensed under the Apache License, Version 2.0 (the "License"); 

5# you may not use this file except in compliance with the License. 

6# You may obtain a copy of the License at 

7# 

8# http://www.apache.org/licenses/LICENSE-2.0 

9# 

10# Unless required by applicable law or agreed to in writing, software 

11# distributed under the License is distributed on an "AS IS" BASIS, 

12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

13# See the License for the specific language governing permissions and 

14# limitations under the License. 

15 

16import builtins 1ab

17import os 1ab

18 

19import wx 1ab

20import wx.lib.agw.aui as aui 1ab

21from wx import Colour 1ab

22from wx.adv import TaskBarIcon, TBI_DOCK, EVT_TASKBAR_LEFT_DOWN 1ab

23from multiprocessing import shared_memory 1ab

24 

25from .actiontriggers import (MenuBar, ToolBarButton, ShortcutRegistry, _RideSearchMenuItem) 1ab

26from .filedialogs import (NewProjectDialog, InitFileFormatDialog) 1ab

27from .fileexplorerplugin import FileExplorer 1ab

28from .notebook import NoteBook 1ab

29from .pluginmanager import PluginManager 1ab

30from .progress import LoadProgressObserver 1ab

31from .review import ReviewDialog 1ab

32from .treeplugin import Tree 1ab

33from ..action import action_info_collection, action_factory, SeparatorInfo 1ab

34from ..action.shortcut import localize_shortcuts 1ab

35from ..context import get_about_ride, SHORTCUT_KEYS, IS_WINDOWS 1ab

36from ..controller.ctrlcommands import SaveFile, SaveAll 1ab

37from ..editor import customsourceeditor 1ab

38from ..preferences import PreferenceEditor 1ab

39from ..publish import (RideSaveAll, RideClosing, RideSaved, PUBLISHER, RideInputValidationError, RideTreeSelection, 1ab

40 RideModificationPrevented, RideBeforeSaving, RideSettingsChanged) 

41from ..ui.filedialogs import RobotFilePathDialog 1ab

42from ..ui.tagdialogs import ViewAllTagsDialog 1ab

43from ..utils import RideFSWatcherHandler 1ab

44from ..widgets import RIDEDialog, ImageProvider, HtmlWindow 1ab

45 

46_ = wx.GetTranslation # To keep linter/code analyser happy 1ab

47builtins.__dict__['_'] = wx.GetTranslation 1ab

48 

49ID_CustomizeToolbar = wx.ID_HIGHEST + 1 1ab

50ID_SampleItem = ID_CustomizeToolbar + 1 1ab

51MAINFRAME_POSITION = 'mainframe position' 1ab

52MAINFRAME_MAXIMIZED = 'mainframe maximized' 1ab

53 

54 

55def get_menudata(): 1ab

56 # Menus to translate 

57 file_0 = _("[File]\n") 

58 file_1 = _("!&New Project | Create a new top level suite | Ctrlcmd-N | ART_NEW\n") 

59 separator = "---\n" 

60 file_2 = _("!&Open Test Suite | Open file containing tests | Ctrlcmd-O | ART_FILE_OPEN\n") 

61 file_3 = _("!Open &Directory | Open directory containing datafiles | Shift-Ctrlcmd-O | ART_FOLDER_OPEN\n") 

62 file_4 = _("!Open External File | Open file in Code Editor | | ART_NORMAL_FILE\n") 

63 file_5 = _("!&Save | Save selected datafile | Ctrlcmd-S | ART_FILE_SAVE\n") 

64 file_6 = _("!Save &All | Save all changes | Ctrlcmd-Shift-S | ART_FILE_SAVE_AS\n") 

65 file_7 = _("!E&xit | Exit RIDE | Ctrlcmd-Q\n") 

66 tool_0 = _("[Tools]\n") 

67 tool_1 = _("!Search Unused Keywords | | | | POSITION-54\n") 

68 tool_2 = _("!Manage Plugins | | | | POSITION-81\n") 

69 tool_3 = _("!View All Tags | | F7 | | POSITION-82\n") 

70 tool_4 = _("!Preferences | | | | POSITION-99\n") 

71 help_0 = _("[Help]\n") 

72 help_1 = _("!Shortcut keys | RIDE shortcut keys\n") 

73 help_2 = _("!User Guide | Robot Framework User Guide\n") 

74 help_3 = _("!Wiki | RIDE User Guide (Wiki)\n") 

75 help_4 = _("!Report a Problem | Open browser to SEARCH on the RIDE issue tracker\n") 

76 help_6 = _("!About | Information about RIDE\n") 

77 help_7 = _("!Check for Upgrade | Looks at PyPi for new released version\n") 

78 

79 return (file_0 + file_1 + separator + file_2 + file_3 + file_4 + separator + file_5 + file_6 + separator + 

80 file_7 + '\n' + tool_0 + tool_1 + tool_2 + tool_3 + tool_4 + '\n' + help_0 + help_1 + help_2 + 

81 help_3 + help_4 + help_6 + help_7) 

82 

83 

84class RideFrame(wx.Frame): 1ab

85 

86 _menudata_nt = """[File] 1ab

87 !&New Project | Create a new top level suite | Ctrlcmd-N | ART_NEW 

88 --- 

89 !&Open Test Suite | Open file containing tests | Ctrlcmd-O | ART_FILE_OPEN 

90 !Open &Directory | Open directory containing datafiles | Shift-Ctrlcmd-O | ART_FOLDER_OPEN 

91 !Open External File | Open file in Code Editor | | ART_NORMAL_FILE 

92 --- 

93 !&Save | Save selected datafile | Ctrlcmd-S | ART_FILE_SAVE 

94 !Save &All | Save all changes | Ctrlcmd-Shift-S | ART_FILE_SAVE_AS 

95 --- 

96 !E&xit | Exit RIDE | Ctrlcmd-Q 

97 

98 [Tools] 

99 !Search Unused Keywords | | | | POSITION-54 

100 !Manage Plugins | | | | POSITION-81 

101 !View All Tags | | F7 | | POSITION-82 

102 !Preferences | | | | POSITION-99 

103 

104 [Help] 

105 !Shortcut keys | RIDE shortcut keys 

106 !User Guide | Robot Framework User Guide 

107 !Wiki | RIDE User Guide (Wiki) 

108 !Report a Problem | Open browser to SEARCH on the RIDE issue tracker 

109 !About | Information about RIDE 

110 !Check for Upgrade | Looks at PyPi for new released version 

111 """ 

112 

113 def __init__(self, application, controller): 1ab

114 size = application.settings.get('mainframe size', (1100, 700)) 

115 # DEBUG self.general_settings = application.settings['General'] 

116 wx.Frame.__init__(self, parent=None, id=wx.ID_ANY, title='RIDE', 

117 pos=application.settings.get(MAINFRAME_POSITION, (50, 30)), 

118 size=size, style=wx.DEFAULT_FRAME_STYLE | wx.SUNKEN_BORDER | wx.BORDER_THEME) 

119 

120 # Shared memory to store language definition 

121 try: 

122 self.sharemem = shared_memory.ShareableList(['en'], name="language") 

123 except FileExistsError: # Other instance created file 

124 self.sharemem = shared_memory.ShareableList(name="language") 

125 # set Left to Right direction (while we don't have localization) 

126 self.SetLayoutDirection(wx.Layout_LeftToRight) 

127 # self.SetLayoutDirection(wx.Layout_RightToLeft) 

128 

129 self.fontinfo = application.fontinfo 

130 self.SetFont(wx.Font(self.fontinfo)) 

131 

132 self.aui_mgr = aui.AuiManager(self) 

133 

134 # tell AuiManager to manage this frame 

135 self.aui_mgr.SetManagedWindow(self) 

136 

137 self.SetMinSize(wx.Size(400, 300)) 

138 

139 self.ensure_on_screen() 

140 if application.settings.get(MAINFRAME_MAXIMIZED, False): 140 ↛ 141line 140 didn't jump to line 141 because the condition on line 140 was never true

141 self.Maximize() 

142 self._application = application 

143 self.controller = controller 

144 self._image_provider = ImageProvider() 

145 self.reformat = application.settings.get('reformat', False) 

146 self.tasks = application.settings.get('tasks', False) 

147 self.doc_language = application.settings.get('doc language', '') 

148 self._notebook_theme = application.settings.get('notebook theme', 0) 

149 self.general_settings = application.settings['General'] # .get_without_default('General') 

150 self.color_background_help = self.general_settings.get('background help', (240, 242, 80)) 

151 self.color_foreground_text = self.general_settings.get('foreground text', (7, 0, 70)) 

152 self.color_background = self.general_settings.get_without_default('background') 

153 self.color_foreground = self.general_settings.get_without_default('foreground') 

154 self.font_face = self.general_settings.get('font face', '') 

155 self.font_size = self.general_settings.get('font size', 11) 

156 self.ui_language = self.general_settings.get('ui language', 'English') 

157 self.main_menu = None 

158 self._init_ui() 

159 self._task_bar_icon = RIDETaskBarIcon(self, self._image_provider) 

160 self._plugin_manager = PluginManager(self.notebook) 

161 self._review_dialog = None 

162 self._view_all_tags_dialog = None 

163 self._current_external_dir = None 

164 self.Bind(wx.EVT_CLOSE, self.on_close) 

165 self.Bind(wx.EVT_SIZE, self.on_size) 

166 self.Bind(wx.EVT_MOVE, self.on_move) 

167 self.Bind(wx.EVT_MAXIMIZE, self.on_maximize) 

168 self.Bind(wx.EVT_DIRCTRL_FILEACTIVATED, self.on_open_file) 

169 self.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK, self.on_menu_open_file) 

170 self._subscribe_messages() 

171 wx.CallAfter(self.actions.register_tools) # DEBUG 

172 # DEBUG wx.CallAfter(self.OnSettingsChanged, self.general_settings) 

173 

174 def _subscribe_messages(self): 1ab

175 for listener, topic in [ 

176 (lambda message: self.SetStatusText(_('Saved %s') % message.path), RideSaved), 

177 (lambda message: self.SetStatusText(_('Saved all files')), RideSaveAll), 

178 (self._set_label, RideTreeSelection), 

179 (self._show_validation_error, RideInputValidationError), 

180 (self._show_modification_prevented_error, RideModificationPrevented), 

181 (self.on_ui_language_changed, RideSettingsChanged) 

182 ]: 

183 PUBLISHER.subscribe(listener, topic) 

184 

185 def _set_label(self, message): 1ab

186 self.SetTitle(self._create_title(message)) 

187 

188 @staticmethod 1ab

189 def _create_title(message): 1ab

190 title = 'RIDE' 

191 if message: 

192 item = message.item 

193 title += ' - ' + item.datafile.name 

194 if not item.is_modifiable(): 

195 title += _(' (READ ONLY)') 

196 return title 

197 

198 @staticmethod 1ab

199 def _show_validation_error(message): 1ab

200 message_box = RIDEDialog(title=_('Validation Error'), message=message.message, style=wx.ICON_ERROR|wx.OK) 

201 message_box.ShowModal() 

202 

203 @staticmethod 1ab

204 def _show_modification_prevented_error(message): 1ab

205 message_box = RIDEDialog(title=_("Modification prevented"), 

206 message=_("\"%s\" is read only") % message.controller.datafile_controller.filename, 

207 style=wx.ICON_ERROR|wx.OK) 

208 # message_box.CenterOnParent() 

209 message_box.ShowModal() 

210 

211 def _init_ui(self): 1ab

212 """ DEBUG: 

213 self.aui_mgr.AddPane(wx.Panel(self), aui.AuiPaneInfo().CenterPane()) 

214 self.splitter = wx.SplitterWindow(self, style=wx.SP_LIVE_UPDATE) 

215 """ 

216 if not self.main_menu: 216 ↛ 219line 216 didn't jump to line 219 because the condition on line 216 was always true

217 new_ui = True 

218 else: 

219 new_ui = False 

220 if new_ui: # Only when creating UI we add panes 220 ↛ 235line 220 didn't jump to line 235 because the condition on line 220 was always true

221 self.aui_mgr.AddPane(wx.Panel(self), aui.AuiPaneInfo().Name("right_pane").Right()) 

222 # set up default notebook style 

223 self._notebook_style = (aui.AUI_NB_DEFAULT_STYLE | aui.AUI_NB_WINDOWLIST_BUTTON | aui.AUI_NB_TAB_FLOAT | 

224 aui.AUI_NB_TAB_EXTERNAL_MOVE | aui.AUI_NB_SUB_NOTEBOOK | aui.AUI_NB_SMART_TABS) 

225 self._notebook_style ^= aui.AUI_NB_TAB_FIXED_WIDTH 

226 # DEBUG: self._notebook_theme = 0 (allow to select themes for notebooks) 

227 # DEBUG:self.notebook = NoteBook(self.splitter, self._application, self._notebook_style) 

228 self.notebook = NoteBook(self, self._application, self._notebook_style) 

229 self.notebook.SetFont(wx.Font(self.fontinfo)) 

230 self.notebook.SetBackgroundColour(Colour(self.color_background)) 

231 self.notebook.SetForegroundColour(Colour(self.color_foreground)) 

232 self.aui_mgr.AddPane(self.notebook, aui.AuiPaneInfo().Name("notebook_editors"). 

233 CenterPane().PaneBorder(True)) 

234 # we need to remake Menu if language changes 

235 if self.main_menu: 235 ↛ 236line 235 didn't jump to line 236 because the condition on line 235 was never true

236 del self.main_menu 

237 pane = self.aui_mgr.GetPaneByName("maintoolbar") 

238 self.aui_mgr.DetachPane(pane) 

239 pane.DestroyOnClose(True) 

240 self.aui_mgr.ClosePane(pane) 

241 del pane 

242 # DEBUG: del self.toolbar 

243 

244 _menudata = get_menudata() 

245 

246 self.main_menu = MenuBar(self) 

247 self.main_menu.take_menu_bar_into_use() 

248 self.toolbar = ToolBar(self) 

249 self.toolbar.SetMinSize(wx.Size(100, 60)) 

250 self.toolbar.SetBackgroundColour(Colour(self.color_background)) 

251 self.toolbar.SetForegroundColour(Colour(self.color_foreground)) 

252 # self.SetToolBar(self.toolbar.GetToolBar()) 

253 self.main_menu._mb.SetFont(wx.Font(self.fontinfo)) 

254 self.main_menu.m_frame.SetFont(wx.Font(self.fontinfo)) 

255 self.main_menu.m_frame.SetBackgroundColour(Colour(self.color_background)) 

256 self.main_menu.m_frame.SetForegroundColour(Colour(self.color_foreground)) 

257 

258 self.aui_mgr.AddPane(self.toolbar, aui.AuiPaneInfo().Name("maintoolbar").ToolbarPane().Top()) 

259 self.actions = ActionRegisterer(self.aui_mgr, self.main_menu, self.toolbar, ShortcutRegistry(self)) 

260 """ 

261 ##### Test 

262 tb3 = self.testToolbar() 

263 

264 self.aui_mgr.AddPane(tb3, 

265 aui.AuiPaneInfo().Name("tb3").Caption("Toolbar 3"). 

266 ToolbarPane().Top().Row(1).Position(1)) 

267 

268 ##### End Test 

269 """ 

270 # DEBUG: self.leftpanel = wx.Panel(self, name="left_panel", size = (275, 250)) 

271 if new_ui: # Only when creating UI we add panes 271 ↛ 284line 271 didn't jump to line 284 because the condition on line 271 was always true

272 # Tree is always created here 

273 self.tree = Tree(self, self.actions, self._application.settings) 

274 self.tree.SetMinSize(wx.Size(275, 250)) 

275 self.tree.SetFont(wx.Font(self.fontinfo)) 

276 # self.leftpanel.Bind(wx.EVT_SIZE, self.tree.OnSize) 

277 # self.aui_mgr.AddPane(self.leftpanel, aui.AuiPaneInfo().Name("left_panel").Caption("left_panel").Left()) 

278 # DEBUG: Next was already called from application.py 

279 self.aui_mgr.AddPane(self.tree, 

280 aui.AuiPaneInfo().Name("tree_content").Caption(_("Test Suites")).CloseButton(False). 

281 LeftDockable()) # DEBUG: remove .CloseButton(False) when restore is fixed 

282 # DEBUG: self.aui_mgr.GetPane(self.tree).DestroyOnClose() 

283 # TreePlugin will manage showing the Tree 

284 self.actions.register_actions(action_info_collection(_menudata, self, data_nt=self._menudata_nt, 

285 container=self.tree)) 

286 if new_ui: # Only when creating UI we add panes 286 ↛ 295line 286 didn't jump to line 295 because the condition on line 286 was always true

287 # ##### File explorer panel is always created here 

288 self.filemgr = FileExplorer(self, self.controller) 

289 self.filemgr.SetFont(wx.Font(self.fontinfo)) 

290 self.filemgr.SetMinSize(wx.Size(275, 250)) 

291 # DEBUG: Next was already called from application.py 

292 self.aui_mgr.AddPane(self.filemgr, aui.AuiPaneInfo().Name("file_manager").LeftDockable()) 

293 

294 # self.main_menu.take_menu_bar_into_use() 

295 if new_ui: # Only when creating UI we add panes 295 ↛ 301line 295 didn't jump to line 301 because the condition on line 295 was always true

296 self.CreateStatusBar(number=2, name="StatusBar") 

297 self._status_bar = self.FindWindowByName("StatusBar", self) 

298 self._status_bar.SetStatusWidths([-2, -3]) 

299 # set main frame icon 

300 self.SetIcons(self._image_provider.PROGICONS) 

301 if self._status_bar: 301 ↛ 308line 301 didn't jump to line 308 because the condition on line 301 was always true

302 self.SetStatusBarPane(0) 

303 self._status_bar.SetFont(wx.Font(self.fontinfo)) 

304 self._status_bar.SetBackgroundColour(Colour(self.color_background)) 

305 self._status_bar.SetForegroundColour(Colour(self.color_foreground)) 

306 self._status_bar.SetOwnForegroundColour(Colour(self.color_foreground)) 

307 # change notebook theme 

308 self.set_notebook_theme() 

309 # tell the manager to "commit" all the changes just made 

310 self.aui_mgr.Update() 

311 # wx.CallLater(2000, RideSettingsChanged(keys=("General", ''), old='', new='').publish) 

312 

313 def set_notebook_theme(self): 1ab

314 if not self.notebook: 314 ↛ 315line 314 didn't jump to line 315 because the condition on line 314 was never true

315 return 

316 try: 

317 self._notebook_theme = int(self._notebook_theme) 

318 except ValueError: 

319 self._notebook_theme = 0 

320 if self._notebook_theme == 1: 320 ↛ 321line 320 didn't jump to line 321 because the condition on line 320 was never true

321 self.notebook.SetArtProvider(aui.AuiSimpleTabArt()) 

322 elif self._notebook_theme == 2: 322 ↛ 323line 322 didn't jump to line 323 because the condition on line 322 was never true

323 self.notebook.SetArtProvider(aui.VC71TabArt()) 

324 elif self._notebook_theme == 3: 324 ↛ 325line 324 didn't jump to line 325 because the condition on line 324 was never true

325 self.notebook.SetArtProvider(aui.FF2TabArt()) 

326 elif self._notebook_theme == 4: 326 ↛ 327line 326 didn't jump to line 327 because the condition on line 326 was never true

327 self.notebook.SetArtProvider(aui.VC8TabArt()) 

328 elif self._notebook_theme == 5: 

329 self.notebook.SetArtProvider(aui.ChromeTabArt()) 

330 else: 

331 self.notebook.SetArtProvider(aui.AuiDefaultTabArt()) 

332 

333 def get_selected_datafile(self): 1ab

334 return self.tree.get_selected_datafile() 

335 

336 def get_selected_datafile_controller(self): 1ab

337 return self.tree.get_selected_datafile_controller() 

338 

339 def on_close(self, event): 1ab

340 from ..preferences import RideSettings 

341 if self._allowed_to_exit(): 

342 try: 

343 perspective = self.aui_mgr.SavePerspective() 

344 # Next restore of settings is because we may have edited from Preferences 

345 self._application.settings = RideSettings(self._application.settings_path) 

346 self._application.settings.set('AUI Perspective', perspective) 

347 # deinitialize the frame manager 

348 self.aui_mgr.UnInit() 

349 del self.aui_mgr 

350 except AttributeError: 

351 pass 

352 try: 

353 nb_perspective = self.notebook.SavePerspective() 

354 self._application.settings.set('AUI NB Perspective', nb_perspective) 

355 except AttributeError: 

356 pass 

357 PUBLISHER.unsubscribe(self._set_label, RideTreeSelection) 

358 RideClosing().publish() 

359 # DEBUG: Wrap in try/except for RunTime error 

360 try: 

361 self._task_bar_icon.RemoveIcon() 

362 self._task_bar_icon.Destroy() 

363 except RuntimeError: 

364 pass 

365 try: 

366 PUBLISHER.unsubscribe_all() 

367 self.Destroy() 

368 wx.Exit() 

369 except RuntimeError: 

370 pass 

371 app = wx.GetApp() 

372 if app is not self._application: 

373 # other wx app instance created unexpectedly 

374 # this will cause RIDE app instance cannot invoke ExitMainLoop properly 

375 self._application.ExitMainLoop() 

376 wx.Exit() 

377 else: 

378 wx.CloseEvent.Veto(event) 

379 

380 def on_size(self, event): 1ab

381 if wx.VERSION >= (4, 1, 0): 381 ↛ 384line 381 didn't jump to line 384 because the condition on line 381 was always true1ac

382 size = self.DoGetSize() 1ac

383 else: 

384 size = tuple(self.GetSize()) 

385 is_full_screen_mode = size == wx.DisplaySize() 1ac

386 self._application.settings[MAINFRAME_MAXIMIZED] = self.IsMaximized() or is_full_screen_mode 1ac

387 if not is_full_screen_mode: 387 ↛ 389line 387 didn't jump to line 389 because the condition on line 387 was always true1ac

388 self._application.settings['mainframe size'] = size 1ac

389 if wx.VERSION >= (4, 1, 0): 389 ↛ 392line 389 didn't jump to line 392 because the condition on line 389 was always true1ac

390 self._application.settings[MAINFRAME_POSITION] = self.DoGetPosition() 1ac

391 else: 

392 self._application.settings[MAINFRAME_POSITION] = tuple(self.GetPosition()) 

393 event.Skip() 1ac

394 

395 def on_move(self, event): 1ab

396 # When the window is Iconized, a move event is also raised, but we 

397 # don't want to update the position in the settings file 

398 if not self.IsIconized() and not self.IsMaximized(): 

399 if wx.VERSION >= (4, 1, 0): 

400 self._application.settings[MAINFRAME_POSITION] = self.DoGetPosition() 

401 else: 

402 self._application.settings[MAINFRAME_POSITION] = tuple(self.GetPosition()) 

403 event.Skip() 

404 

405 def on_maximize(self, event): 1ab

406 self._application.settings[MAINFRAME_MAXIMIZED] = True 

407 event.Skip() 

408 

409 def _allowed_to_exit(self): 1ab

410 if self.has_unsaved_changes(): 

411 message_box = RIDEDialog(title=_('Warning'), message=_("There are unsaved modifications.\n" 

412 "Do you want to save your changes before " 

413 "exiting?"), style=wx.ICON_WARNING | wx.CANCEL | wx.YES_NO) 

414 ret = message_box.execute() 

415 if ret in (wx.CANCEL, wx.ID_CANCEL): 

416 return False 

417 if ret in (wx.YES, wx.ID_YES, wx.OK, wx.ID_OK): 

418 self.save() 

419 return True 

420 

421 def has_unsaved_changes(self): 1ab

422 return self.controller.is_dirty() 

423 

424 def on_new_project(self, event): 1ab

425 __ = event 

426 if not self.check_unsaved_modifications(): 

427 return 

428 NewProjectDialog(self.controller).execute() 

429 self._populate_tree() 

430 

431 def _populate_tree(self): 1ab

432 self.tree.populate(self.controller) 

433 self.filemgr.update_tree() 

434 

435 def on_open_file(self, event): 1ab

436 __ = event 

437 if not self.filemgr: 

438 return 

439 # EVT_DIRCTRL_FILEACTIVATED 

440 from os.path import splitext 

441 robottypes = self._application.settings.get('robot types', ['robot', 

442 'resource', 

443 'txt', 

444 'tsv']) # Removed 'html' 

445 path = self.filemgr.GetFilePath() 

446 ext = '' 

447 if len(path) > 0: 

448 ext = splitext(path) 

449 ext = ext[1].replace('.', '') 

450 # print("DEBUG: path %s ext %s" % (path, ext)) 

451 if len(ext) > 0 and ext in robottypes: 

452 if not self.check_unsaved_modifications(): 

453 return 

454 if self.open_suite(path): 

455 return 

456 customsourceeditor.main(path) 

457 

458 def on_menu_open_file(self, event): 1ab

459 if not self.filemgr: 

460 return 

461 # DEBUG: Use widgets/popupmenu tools 

462 path = self.filemgr.GetFilePath() 

463 if len(path) > 0: 

464 self.on_open_file(event) 

465 else: 

466 path = self.filemgr.GetPath() 

467 if not self.check_unsaved_modifications(): 

468 return 

469 self.open_suite(path) # It is a directory, do not edit 

470 event.Skip() 

471 

472 def on_open_external_file(self, event): 1ab

473 __ = event 

474 if not self._current_external_dir: 

475 curdir = self.controller.default_dir 

476 else: 

477 curdir = self._current_external_dir 

478 fdlg = wx.FileDialog(self, defaultDir=curdir, style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) 

479 if fdlg.ShowModal() == wx.ID_CANCEL: 

480 return 

481 path = fdlg.GetPath() 

482 try: 

483 self._current_external_dir = os.path.dirname(path) 

484 customsourceeditor.main(path) 

485 except IOError: 

486 wx.LogError(f"Cannot open file {path}") 

487 

488 def on_open_test_suite(self, event): 1ab

489 __ = event 

490 if not self.check_unsaved_modifications(): 

491 return 

492 path = RobotFilePathDialog( 

493 self, self.controller, self._application.settings).execute() 

494 if path: 

495 if self.open_suite(path): 

496 return 

497 customsourceeditor.main(path) 

498 

499 def check_unsaved_modifications(self): 1ab

500 if self.has_unsaved_changes(): 

501 message_box = RIDEDialog(title=_("Warning"), message=_("There are unsaved modifications.\n" 

502 "Do you want to proceed without saving?"), style=wx.ICON_WARNING | wx.YES_NO) 

503 ret = message_box.ShowModal() 

504 return ret == wx.ID_YES 

505 return True 

506 

507 def open_suite(self, path): 1ab

508 self.controller.update_default_dir(path) 

509 # self._controller.default_dir will only save dir path 

510 # need to save path to self._application.workspace_path too 

511 self._application.workspace_path = path 

512 if IS_WINDOWS: 

513 self._application.changed_workspace = True 

514 from ..lib.compat.parsing.language import check_file_language 

515 self.controller.file_language = check_file_language(path) 

516 set_lang = [] 

517 set_lang.append('en') 

518 try: 

519 set_lang = shared_memory.ShareableList(name="language") 

520 except FileNotFoundError: 

521 set_lang[0] = 'en' 

522 if self.controller.file_language: 

523 set_lang[0] = self.controller.file_language[0] 

524 # print(f"DEBUG: project.py Project load_data file_language = {self.controller.file_language}\n" 

525 # f"sharedmem={set_lang}") 

526 else: 

527 set_lang[0] = 'en' 

528 try: 

529 err = self.controller.load_datafile(path, LoadProgressObserver(self, background=self.color_background, 

530 foreground=self.color_foreground)) 

531 if isinstance(err, UserWarning): 

532 # DEBUG: raise err # Just leave message in Parser Log 

533 return False 

534 except UserWarning: 

535 return False 

536 self._populate_tree() 

537 if IS_WINDOWS: 

538 wx.CallLater(60000, self.clear_workspace_state) 

539 return True 

540 

541 def clear_workspace_state(self): 1ab

542 self._application.changed_workspace = False 

543 

544 def refresh_datafile(self, item, event): 1ab

545 self.tree.refresh_datafile(item, event) 

546 if self.filemgr: 

547 self.filemgr.ReCreateTree() 

548 

549 def on_open_directory(self, event): 1ab

550 __ = event 

551 if self.check_unsaved_modifications(): 

552 path = wx.DirSelector(message=_("Choose a directory containing Robot files"), 

553 default_path=self.controller.default_dir) 

554 if path: 

555 self.open_suite(path) 

556 

557 def on_save(self, event): 1ab

558 __ = event 

559 RideBeforeSaving().publish() 

560 self.save() 

561 

562 def on_save_all(self, event): 1ab

563 __ = event 

564 RideBeforeSaving().publish() 

565 self.save_all() 

566 

567 def save_all(self): 1ab

568 self._show_dialog_for_files_without_format() 

569 self.controller.execute(SaveAll(self.reformat)) 

570 

571 def save(self, controller=None): 1ab

572 if controller is None: 

573 controller = self.get_selected_datafile_controller() 

574 if controller is not None: 

575 if not controller.has_format(): 

576 self._show_dialog_for_files_without_format(controller) 

577 else: 

578 controller.execute(SaveFile(self.reformat)) 

579 

580 def _show_dialog_for_files_without_format(self, controller=None): 1ab

581 files_without_format = self.controller.get_files_without_format( 

582 controller) 

583 for f in files_without_format: 

584 self._show_format_dialog_for(f) 

585 

586 @staticmethod 1ab

587 def _show_format_dialog_for(file_controller_without_format): 1ab

588 InitFileFormatDialog(file_controller_without_format).execute() 

589 

590 def on_exit(self, event): 1ab

591 __ = event 

592 try: 

593 self.sharemem.shm.close() 

594 self.sharemem.shm.unlink() 

595 except FileNotFoundError: 

596 pass 

597 self.Close() 

598 

599 def on_manage_plugins(self, event): 1ab

600 __ = event 

601 self._plugin_manager.show(self._application.get_plugins()) 

602 

603 def on_view_all_tags(self, event): 1ab

604 __ = event 

605 if self._view_all_tags_dialog is None: 

606 self._view_all_tags_dialog = ViewAllTagsDialog(self.controller, self) 

607 self._view_all_tags_dialog.show_dialog() 

608 

609 def on_search_unused_keywords(self, event): 1ab

610 __ = event 

611 if self._review_dialog is None: 

612 self._review_dialog = ReviewDialog(self.controller, self) 

613 self._review_dialog.show_dialog() 

614 

615 def on_preferences(self, event): 1ab

616 __ = event 

617 dlg = PreferenceEditor(self, _("RIDE - Preferences"), 

618 self._application.preferences, style='tree') 

619 # Changed to non-modal, original comment follows: 

620 # I would prefer that this not be modal, but making it non-modal 

621 # opens up a can of worms. We don't want to have to deal 

622 # with settings getting changed out from under us while the 

623 # dialog is open. 

624 dlg.Show() 

625 

626 @staticmethod 1ab

627 def on_about(event): 1ab

628 __ = event 

629 dlg = AboutDialog() 

630 dlg.ShowModal() 

631 dlg.Destroy() 

632 

633 def on_check_for_upgrade(self, event): 1ab

634 __ = event 

635 from ..application.updatenotifier import UpdateNotifierController, UpdateDialog 

636 wx.CallAfter(UpdateNotifierController(self.general_settings, self.notebook).notify_update_if_needed, 

637 UpdateDialog, ignore_check_condition=True, show_no_update=True) 

638 

639 @staticmethod 1ab

640 def on_shortcut_keys(event): 1ab

641 __ = event 

642 dialog = ShortcutKeysDialog() 

643 """ DEBUG: 

644 self.aui_mgr.AddPane(dialog.GetContentWindow(),aui.AuiPaneInfo().Name("shortcuts").Caption("Shortcuts Keys") 

645 .CloseButton(True).RightDockable().Floatable().Float(), self.notebook) 

646 self.aui_mgr.Update() 

647 """ 

648 dialog.Show() 

649 

650 @staticmethod 1ab

651 def on_report_a_problem(event): 1ab

652 __ = event 

653 wx.LaunchDefaultBrowser("https://github.com/robotframework/RIDE/issues" 

654 "?utf8=%E2%9C%93&q=is%3Aissue+%22search" 

655 "%20your%20problem%22" 

656 ) 

657 

658 @staticmethod 1ab

659 def on_user_guide(event): 1ab

660 __ = event 

661 wx.LaunchDefaultBrowser("https://robotframework.org/robotframework/#user-guide") 

662 

663 @staticmethod 1ab

664 def on_wiki(event): 1ab

665 __ = event 

666 wx.LaunchDefaultBrowser("https://github.com/robotframework/RIDE/wiki") 

667 

668 def _has_data(self): 1ab

669 return self.controller.data is not None 

670 

671 def _refresh(self): 1ab

672 self.controller.update_namespace() 

673 

674 # This code is copied from http://wiki.wxpython.org/EnsureFrameIsOnScreen, 

675 # and adapted to fit our code style. 

676 def ensure_on_screen(self): 1ab

677 try: 

678 display_id = wx.Display.GetFromWindow(self) 

679 except NotImplementedError: 

680 display_id = 0 

681 if display_id == -1: 681 ↛ 682line 681 didn't jump to line 682 because the condition on line 681 was never true

682 display_id = 0 

683 geometry = wx.Display(display_id).GetGeometry() 

684 position = self.GetPosition() 

685 if position.x < geometry.x: 685 ↛ 686line 685 didn't jump to line 686 because the condition on line 685 was never true

686 position.x = geometry.x 

687 if position.y < geometry.y: 687 ↛ 688line 687 didn't jump to line 688 because the condition on line 687 was never true

688 position.y = geometry.y 

689 size = self.GetSize() 

690 if size.width > geometry.width: 690 ↛ 691line 690 didn't jump to line 691 because the condition on line 690 was never true

691 size.width = geometry.width 

692 position.x = geometry.x 

693 elif position.x + size.width > geometry.x + geometry.width: 693 ↛ 694line 693 didn't jump to line 694 because the condition on line 693 was never true

694 position.x = geometry.x + geometry.width - size.width 

695 if size.height > geometry.height: 695 ↛ 696line 695 didn't jump to line 696 because the condition on line 695 was never true

696 size.height = geometry.height 

697 position.y = geometry.y 

698 elif position.y + size.height > geometry.y + geometry.height: 698 ↛ 699line 698 didn't jump to line 699 because the condition on line 698 was never true

699 position.y = geometry.y + geometry.height - size.height 

700 self.SetPosition(position) 

701 self.SetSize(size) 

702 

703 def show_confirm_reload_dlg(self, event): 1ab

704 msg = [_('Workspace modifications detected on the file system.'), 

705 _('Do you want to reload the workspace?')] 

706 if self.controller.is_dirty(): 

707 msg += [_('Answering <Yes> will discard unsaved changes.'), 

708 _('Answering <No> will ignore the changes on disk.')] 

709 message_box = RIDEDialog(title=_('Files Changed On Disk'), message='\n'.join(msg), 

710 style=wx.ICON_WARNING | wx.YES_NO) 

711 ret = message_box.ShowModal() 

712 confirmed = ret == wx.ID_YES 

713 if confirmed: 

714 # workspace_path should update after open directory/suite 

715 # There are two scenarios: 

716 # 1. path is a directory 

717 # 2. path is a suite file 

718 new_path = RideFSWatcherHandler.get_workspace_new_path() 

719 if new_path and os.path.exists(new_path): 

720 wx.CallAfter(self.open_suite, new_path) 

721 else: 

722 # in case workspace is totally removed 

723 # ask user to open new directory 

724 # DEBUG: add some notification msg to users 

725 wx.CallAfter(self.on_open_directory, event) 

726 

727 def on_ui_language_changed(self, message): 1ab

728 if message.keys[0] != "General": 1ac

729 return 1ac

730 self.ui_language = self.general_settings.get('ui language', 'English') 

731 # print(f"DEBUG: mainframe.py on_ui_language_changed message.items={message.keys}, menudata={get_menudata}\n" 

732 # f"language={self.ui_language} Translated Warning={_('Warning')} ") 

733 # DANGER!!! # The below refresh works, but we lose TestRunner buttons in taskbar and Edit menu is broken 

734 # wx.CallLater(1000, self._init_ui) # Let the change happen at application 

735 

736 

737# Code moved from actiontriggers 

738class ToolBar(aui.AuiToolBar): 1ab

739 

740 def __init__(self, frame): 1ab

741 aui.AuiToolBar.__init__(self, frame) 

742 # prepare a few custom overflow elements for the toolbars' overflow buttons 

743 prepend_items, append_items = [], [] 

744 item = aui.AuiToolBarItem() 

745 

746 item.SetKind(wx.ITEM_SEPARATOR) 

747 append_items.append(item) 

748 

749 item = aui.AuiToolBarItem() 

750 item.SetKind(wx.ITEM_NORMAL) 

751 item.SetId(ID_CustomizeToolbar) 

752 item.SetLabel(_("Customize...")) 

753 append_items.append(item) 

754 

755 self._frame = frame 

756 # DEBUG If we attach to frame it won't be detachable, and overlaps 

757 # If self, buttons are not shown 

758 self.tb = aui.AuiToolBar(self, -1, wx.DefaultPosition, wx.DefaultSize, 

759 agwStyle=aui.AUI_TB_DEFAULT_STYLE | aui.AUI_TB_OVERFLOW) 

760 self.tb.SetToolBitmapSize(wx.Size(16, 16)) 

761 self._buttons = [] 

762 self._search_handlers = {} 

763 self._current_description = None 

764 self.SetMinSize(wx.Size(100, 60)) 

765 self.tb.SetCustomOverflowItems(prepend_items, append_items) 

766 self.tb.Realize() 

767 

768 def register(self, action): 1ab

769 if action.has_icon(): 1ad

770 button = self._get_existing_button(action) 

771 if not button: 771 ↛ 773line 771 didn't jump to line 773 because the condition on line 771 was always true

772 button = self._create_button(action) 

773 button.register(action) 

774 

775 def _get_existing_button(self, action): 1ab

776 for button in self._buttons: 

777 if button.icon == action.icon: 777 ↛ 778line 777 didn't jump to line 778 because the condition on line 777 was never true

778 return button 

779 return None 

780 

781 def enabled_status_changed(self, idd, action): 1ab

782 self.EnableTool(idd, action.is_active()) 

783 

784 def _create_button(self, action): 1ab

785 button = ToolBarButton(self._frame, self, action) 

786 name = self._format_button_tooltip(action) 

787 self.AddTool(button.id, name, action.icon, wx.NullBitmap, 

788 wx.ITEM_NORMAL, name, action.doc) 

789 self.Realize() 

790 self._buttons.append(button) 

791 return button 

792 

793 @staticmethod 1ab

794 def _format_button_tooltip(action): 1ab

795 tooltip = action.name.replace('&', '') 

796 if action.shortcut and action.shortcut.value: 

797 tooltip = '%s (%s)' % (tooltip, action.shortcut.value) 

798 return tooltip 

799 

800 def remove_toolbar_button(self, button): 1ab

801 self._buttons.remove(button) 

802 self.DeleteTool(button.id) 

803 self.Realize() 

804 

805 def register_search_handler(self, description, handler, icon, 1ab

806 default=False): 

807 if default: 

808 self._current_description = description 

809 self._search_handlers[description] = _RideSearchMenuItem(handler, icon) 

810 

811 

812class ActionRegisterer(object): 1ab

813 

814 def __init__(self, aui_mgr, menubar, toolbar, shortcut_registry): 1ab

815 self._aui_mgr = aui_mgr 

816 self._menubar = menubar 

817 self._toolbar = toolbar 

818 self._shortcut_registry = shortcut_registry 

819 self._tools_items = dict() 

820 

821 def register_action(self, action_info, update_aui=True): 1ab

822 menubar_can_be_registered = True 1ad

823 action = action_factory(action_info) 1ad

824 self._shortcut_registry.register(action) 1ad

825 if hasattr(action_info, "menu_name"): 825 ↛ 830line 825 didn't jump to line 830 because the condition on line 825 was always true1ad

826 # print(f"DEBUG: mainframe.py ActionRegister register_action menu_name={action_info.menu_name}") 

827 if action_info.menu_name == _("Tools"): 1ad

828 self._tools_items[action_info.position] = action 1ad

829 menubar_can_be_registered = False 1ad

830 if menubar_can_be_registered: 1ad

831 self._menubar.register(action) 1ad

832 self._toolbar.register(action) 1ad

833 if update_aui: 1ad

834 # tell the manager to "commit" all the changes just made 

835 self._aui_mgr.Update() 1ad

836 return action 1ad

837 

838 def register_tools(self): 1ab

839 separator_action = action_factory(SeparatorInfo(_("Tools"))) 1ac

840 add_separator_after = [_("stop test run"), _("search unused keywords"), 1ac

841 _("preview"), _("view ride log")] 

842 # for key in sorted(self._tools_items.iterkeys()): 

843 # print("DEBUG: at register_tools, tools: %s" % self._tools_items) 

844 for key in sorted(self._tools_items.keys()): # DEBUG Python3 1ac

845 self._menubar.register(self._tools_items[key]) 1ac

846 # print("DEBUG: key=%s name=%s" % (key, self._tools_items[key].name.lower())) 

847 if self._tools_items[key].name.lower() in add_separator_after: 1ac

848 self._menubar.register(separator_action) 1ac

849 

850 def register_actions(self, actions): 1ab

851 for action in actions: 

852 if not isinstance(action, SeparatorInfo): # DEBUG 

853 # print("DEBUG: action=%s" % action.name) 

854 self.register_action(action, update_aui=False) 

855 # tell the manager to "commit" all the changes just made 

856 self._aui_mgr.Update() 

857 

858 def register_shortcut(self, action_info): 1ab

859 action = action_factory(action_info) 1ad

860 self._shortcut_registry.register(action) 1ad

861 return action 1ad

862 

863 

864class AboutDialog(RIDEDialog): 1ab

865 

866 def __init__(self): 1ab

867 RIDEDialog.__init__(self, title='RIDE') 

868 # set Left to Right direction (while we don't have localization) 

869 self.SetLayoutDirection(wx.Layout_LeftToRight) 

870 sizer = wx.BoxSizer(wx.VERTICAL) 

871 content = get_about_ride() 

872 sizer.Add(HtmlWindow(self, (650, 350), content), 1, flag=wx.EXPAND) 

873 self.SetSizerAndFit(sizer) 

874 

875 def on_key(self, *args): 1ab

876 """ Just ignore keystrokes """ 

877 pass 

878 

879 

880class ShortcutKeysDialog(RIDEDialog): 1ab

881 

882 def __init__(self): 1ab

883 RIDEDialog.__init__(self, title=_("Shortcut keys for RIDE")) 

884 # set Left to Right direction (while we don't have localization) 

885 self.SetLayoutDirection(wx.Layout_LeftToRight) 

886 sizer = wx.BoxSizer(wx.HORIZONTAL) 

887 sizer.Add(HtmlWindow(self, (350, 400), 

888 self._get_platform_specific_shortcut_keys()), 1, 

889 flag=wx.EXPAND) 

890 self.SetSizerAndFit(sizer) 

891 

892 def on_key(self, *args): 1ab

893 """ Just ignore keystrokes """ 

894 pass 

895 

896 @staticmethod 1ab

897 def _get_platform_specific_shortcut_keys(): 1ab

898 return localize_shortcuts(SHORTCUT_KEYS) 

899 

900 

901class RIDETaskBarIcon(TaskBarIcon): 1ab

902 

903 def __init__(self, frame, img_provider): 1ab

904 TaskBarIcon.__init__(self, TBI_DOCK) 

905 self.frame = frame 

906 self._img_provider = img_provider 

907 self.SetIcon(wx.Icon(self._img_provider.RIDE_ICON), "RIDE") 

908 self.Bind(EVT_TASKBAR_LEFT_DOWN, self.on_click) 

909 self.Bind(wx.EVT_MENU, self.on_task_bar_activate, id=1) 

910 self.Bind(wx.EVT_MENU, self.on_task_bar_deactivate, id=2) 

911 self.Bind(wx.EVT_MENU, self.on_task_bar_close, id=3) 

912 

913 def on_click(self, event): 1ab

914 __ = event 

915 self.frame.Raise() 

916 self.frame.Restore() 

917 self.frame.Show(True) 

918 

919 def CreatePopupMenu(self): 1ab

920 menu = wx.Menu() 

921 menu.Append(1, _('Show')) 

922 menu.Append(2, _('Hide')) 

923 menu.Append(3, _('Close')) 

924 return menu 

925 

926 def on_task_bar_close(self, event): 1ab

927 __ = event 

928 self.frame.Close() 

929 

930 def on_task_bar_activate(self, event): 1ab

931 __ = event 

932 if not self.frame.IsShown(): 

933 self.frame.Show() 

934 self.frame.Restore() 

935 

936 def on_task_bar_deactivate(self, event): 1ab

937 __ = event 

938 if self.frame.IsShown(): 

939 self.frame.Hide()