Robot Framework Integrated Development Environment (RIDE)
mainframe.py
Go to the documentation of this file.
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 
16 import os
17 
18 import wx
19 import wx.lib.agw.aui as aui
20 from wx import Colour
21 from wx.adv import TaskBarIcon, TBI_DOCK
22 
23 from .actiontriggers import (MenuBar, ToolBarButton, ShortcutRegistry, _RideSearchMenuItem)
24 from .filedialogs import (NewProjectDialog, InitFileFormatDialog)
25 from .fileexplorerplugin import FileExplorer
26 from .notebook import NoteBook
27 from .pluginmanager import PluginManager
28 from .progress import LoadProgressObserver
29 from .review import ReviewDialog
30 from .treeplugin import Tree
31 from ..action import ActionInfoCollection, ActionFactory, SeparatorInfo
32 from ..action.shortcut import localize_shortcuts
33 from ..context import ABOUT_RIDE, SHORTCUT_KEYS, IS_MAC
34 from ..controller.ctrlcommands import SaveFile, SaveAll
35 from ..editor import customsourceeditor
36 from ..preferences import PreferenceEditor
37 from ..preferences.settings import RideSettings, _Section
38 from ..publish import (RideSaveAll, RideClosing, RideSaved, PUBLISHER, RideInputValidationError, RideTreeSelection,
39  RideModificationPrevented, RideBeforeSaving, RideSettingsChanged)
40 from ..ui.filedialogs import RobotFilePathDialog
41 from ..ui.tagdialogs import ViewAllTagsDialog
42 from ..utils import RideFSWatcherHandler
43 from ..widgets import RIDEDialog, ImageProvider, HtmlWindow
44 
45 
48 _menudata = """
49 [File]
50 !&New Project | Create a new top level suite | Ctrlcmd-N | ART_NEW
51 ---
52 !&Open Test Suite | Open file containing tests | Ctrlcmd-O | ART_FILE_OPEN
53 !Open &Directory | Open directory containing datafiles | Shift-Ctrlcmd-O | ART_FOLDER_OPEN
54 !Open External File | Open file in Code Editor | | ART_NORMAL_FILE
55 ---
56 !&Save | Save selected datafile | Ctrlcmd-S | ART_FILE_SAVE
57 !Save &All | Save all changes | Ctrlcmd-Shift-S | ART_FILE_SAVE_AS
58 ---
59 !E&xit | Exit RIDE | Ctrlcmd-Q
60 
61 [Tools]
62 !Search Unused Keywords | | | | POSITION-54
63 !Manage Plugins | | | | POSITION-81
64 !View All Tags | | F7 | | POSITION-82
65 !Preferences | | | | POSITION-99
66 
67 [Help]
68 !Shortcut keys | RIDE shortcut keys
69 !User Guide | Robot Framework User Guide
70 !Wiki | RIDE User Guide (Wiki)
71 !Report a Problem | Open browser to SEARCH on the RIDE issue tracker
72 !Release notes | Shows release notes
73 !About | Information about RIDE
74 """
75 
76 ID_CustomizeToolbar = wx.ID_HIGHEST + 1
77 ID_SampleItem = ID_CustomizeToolbar + 1
78 
79 
80 """
81 
82 # -- DEBUG some testing
83 # -- SizeReportCtrl --
84 # (a utility control that always reports it's client size)
85 
86 
87 class SizeReportCtrl(wx.Control):
88 
89  def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, mgr=None):
90 
91  wx.Control.__init__(self, parent, id, pos, size, style=wx.NO_BORDER)
92  self._mgr = mgr
93 
94  self.Bind(wx.EVT_PAINT, self.OnPaint)
95  self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
96  self.Bind(wx.EVT_SIZE, self.OnSize)
97 
98  def OnPaint(self, event):
99 
100  dc = wx.PaintDC(self)
101  size = self.GetClientSize()
102 
103  s = "Size: %d x %d" % (size.x, size.y)
104 
105  dc.SetFont(wx.NORMAL_FONT)
106  w, height = dc.GetTextExtent(s)
107  height += 3
108  dc.SetBrush(wx.WHITE_BRUSH)
109  dc.SetPen(wx.WHITE_PEN)
110  dc.DrawRectangle(0, 0, size.x, size.y)
111  dc.SetPen(wx.LIGHT_GREY_PEN)
112  dc.DrawLine(0, 0, size.x, size.y)
113  dc.DrawLine(0, size.y, size.x, 0)
114  dc.DrawText(s, (size.x-w)/2, (size.y-height*5)/2)
115 
116  if self._mgr:
117 
118  pi = self._mgr.GetPane(self)
119 
120  s = "Layer: %d" % pi.dock_layer
121  w, h = dc.GetTextExtent(s)
122  dc.DrawText(s, (size.x-w)/2, ((size.y-(height*5))/2)+(height*1))
123 
124  s = "Dock: %d Row: %d" % (pi.dock_direction, pi.dock_row)
125  w, h = dc.GetTextExtent(s)
126  dc.DrawText(s, (size.x-w)/2, ((size.y-(height*5))/2)+(height*2))
127 
128  s = "Position: %d" % pi.dock_pos
129  w, h = dc.GetTextExtent(s)
130  dc.DrawText(s, (size.x-w)/2, ((size.y-(height*5))/2)+(height*3))
131 
132  s = "Proportion: %d" % pi.dock_proportion
133  w, h = dc.GetTextExtent(s)
134  dc.DrawText(s, (size.x-w)/2, ((size.y-(height*5))/2)+(height*4))
135 
136  def OnEraseBackground(self, event):
137  pass
138 
139  def OnSize(self, event):
140  self.Refresh()
141 """
142 
143 
144 class RideFrame(wx.Frame):
145 
146  def __init__(self, application, controller):
147  size = application.settings.get('mainframe size', (1100, 700))
148  # DEBUG self.general_settings = application.settings['General']
149  wx.Frame.__init__(self, parent=None, id=wx.ID_ANY, title='RIDE',
150  pos=application.settings.get('mainframe position', (50, 30)),
151  size=size, style=wx.DEFAULT_FRAME_STYLE | wx.SUNKEN_BORDER | wx.BORDER_THEME)
152 
153  # set Left to Right direction (while we don't have localization)
154  self.SetLayoutDirection(wx.Layout_LeftToRight)
155  # self.SetLayoutDirection(wx.Layout_RightToLeft)
156 
157  self._mgr_mgr = aui.AuiManager()
158 
159  # tell AuiManager to manage this frame
160  self._mgr_mgr.SetManagedWindow(self)
161 
162  self.SetMinSize(wx.Size(400, 300))
163 
164  self.ensure_on_screenensure_on_screen()
165  if application.settings.get('mainframe maximized', False):
166  self.Maximize()
167  self._application_application = application
168  self._controller_controller = controller
169  self._image_provider_image_provider = ImageProvider()
170  self.reformatreformat = application.settings.get('reformat', False)
171  self.general_settingsgeneral_settings = application.settings['General'] #.get_without_default('General')
172  self.color_background_helpcolor_background_help = self.general_settingsgeneral_settings.get('background help', (240, 242, 80))
173  self.color_foreground_textcolor_foreground_text = self.general_settingsgeneral_settings.get('foreground text', (7, 0, 70))
174  self.color_backgroundcolor_background = self.general_settingsgeneral_settings.get_without_default('background')
175  self.color_foregroundcolor_foreground = self.general_settingsgeneral_settings.get_without_default('foreground')
176  self.font_facefont_face = self.general_settingsgeneral_settings.get('font face', '')
177  self.font_sizefont_size = self.general_settingsgeneral_settings.get('font size', 11)
178  self._init_ui_init_ui()
179  self._task_bar_icon_task_bar_icon = RIDETaskBarIcon(self._image_provider_image_provider)
180  self._plugin_manager_plugin_manager = PluginManager(self.notebooknotebook)
181  self._review_dialog_review_dialog = None
182  self._view_all_tags_dialog_view_all_tags_dialog = None
183  self._current_external_dir_current_external_dir = None
184  self.Bind(wx.EVT_CLOSE, self.OnCloseOnClose)
185  self.Bind(wx.EVT_SIZE, self.OnSizeOnSize)
186  self.Bind(wx.EVT_MOVE, self.OnMoveOnMove)
187  self.Bind(wx.EVT_MAXIMIZE, self.OnMaximizeOnMaximize)
188  self.Bind(wx.EVT_DIRCTRL_FILEACTIVATED, self.OnOpenFileOnOpenFile)
189  self.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK, self.OnMenuOpenFileOnMenuOpenFile)
190  self._subscribe_messages_subscribe_messages()
191  wx.CallAfter(self.actionsactions.register_tools) # DEBUG
192  # DEBUG wx.CallAfter(self.OnSettingsChanged, self.general_settings)
193 
195  for listener, topic in [
196  (lambda message: self.SetStatusText('Saved %s' % message.path), RideSaved),
197  (lambda message: self.SetStatusText('Saved all files'), RideSaveAll),
198  (self._set_label_set_label, RideTreeSelection),
199  (self._show_validation_error_show_validation_error, RideInputValidationError),
200  (self._show_modification_prevented_error_show_modification_prevented_error, RideModificationPrevented),
201  # (self.OnSettingsChanged, RideSettingsChanged)
202  ]:
203  PUBLISHER.subscribe(listener, topic)
204 
205  def _set_label(self, message):
206  self.SetTitle(self._create_title_create_title(message))
207 
208  def _create_title(self, message):
209  title = 'RIDE'
210  if message:
211  item = message.item
212  title += ' - ' + item.datafile.name
213  if not item.is_modifiable():
214  title += ' (READ ONLY)'
215  return title
216 
217  def _show_validation_error(self, message):
218  wx.MessageBox(message.message, 'Validation Error', style=wx.ICON_ERROR)
219 
221  wx.MessageBox("\"%s\" is read only" % message.controller.datafile_controller.filename, "Modification prevented",
222  style=wx.ICON_ERROR)
223 
224  def _init_ui(self):
225  # self._mgr.AddPane(wx.Panel(self), aui.AuiPaneInfo().CenterPane())
226  # #### self.splitter = wx.SplitterWindow(self, style=wx.SP_LIVE_UPDATE)
227  # self._mgr.AddPane(wx.Panel(self), aui.AuiPaneInfo().CenterPane())
228  # set up default notebook style
229  self._notebook_style_notebook_style = aui.AUI_NB_DEFAULT_STYLE | aui.AUI_NB_WINDOWLIST_BUTTON | \
230  aui.AUI_NB_TAB_EXTERNAL_MOVE | aui.AUI_NB_SUB_NOTEBOOK | aui.AUI_NB_SMART_TABS #| wx.NO_BORDER
231  # TODO self._notebook_theme = 0 (allow to select themes for notebooks)
232  # self.notebook = NoteBook(self.splitter, self._application,
233  # self._notebook_style)
234  self.notebooknotebook = NoteBook(self, self._application_application,
235  self._notebook_style_notebook_style)
236  self.notebooknotebook.SetBackgroundColour(Colour(self.color_backgroundcolor_background))
237  self.notebooknotebook.SetForegroundColour(Colour(self.color_foregroundcolor_foreground))
238  self._mgr_mgr.AddPane(self.notebooknotebook,
239  aui.AuiPaneInfo().Name("notebook_editors").
240  CenterPane().PaneBorder(False))
241  # ############### Test
242  # self._mgr.AddPane(self.CreateTextCtrl(),
243  # aui.AuiPaneInfo().Name("text_content").
244  # CenterPane().Hide().MinimizeButton(True))
245  #
246  # self._mgr.AddPane(self.CreateHTMLCtrl(),
247  # aui.AuiPaneInfo().Name("html_content").
248  # CenterPane().Hide().MinimizeButton(True))
249  #
250  # self._mgr.AddPane(self.CreateNotebook(),
251  # aui.AuiPaneInfo().Name("notebook_content").
252  # CenterPane().PaneBorder(False))
253  # ###################
254  # self._mgr.AddPane(self.CreateSizeReportCtrl(), aui.AuiPaneInfo().
255  # Name("test1").Caption(
256  # "Pane Caption").Top().MinimizeButton(True))
257 
258  mb = MenuBar(self)
259  self.toolbartoolbar = ToolBar(self)
260  self.toolbartoolbar.SetMinSize(wx.Size(100, 60))
261  self.toolbartoolbar.SetBackgroundColour(Colour(self.color_backgroundcolor_background))
262  self.toolbartoolbar.SetForegroundColour(Colour(self.color_foregroundcolor_foreground))
263  # self.SetToolBar(self.toolbar.GetToolBar())
264  mb._frame.SetBackgroundColour(Colour(self.color_backgroundcolor_background))
265  mb._frame.SetForegroundColour(Colour(self.color_foregroundcolor_foreground))
266  self._mgr_mgr.AddPane(self.toolbartoolbar, aui.AuiPaneInfo().Name("maintoolbar").
267  ToolbarPane().Top())
268  self.actionsactions = ActionRegisterer(self._mgr_mgr, mb, self.toolbartoolbar,
269  ShortcutRegistry(self))
270  """
271  ##### Test
272  tb3 = self.testToolbar()
273 
274  self._mgr.AddPane(tb3,
275  aui.AuiPaneInfo().Name("tb3").Caption("Toolbar 3").
276  ToolbarPane().Top().Row(1).Position(1))
277 
278  ##### End Test
279  """
280  # self.leftpanel = wx.Panel(self, name="left_panel", size = (275, 250))
281  # Tree is always created here
282  self.treetree = Tree(self, self.actionsactions, self._application_application.settings)
283  self.treetree.SetMinSize(wx.Size(275, 250))
284  # self.leftpanel.Bind(wx.EVT_SIZE, self.tree.OnSize)
285  # self._mgr.AddPane(self.leftpanel, aui.AuiPaneInfo().Name("left_panel").Caption("left_panel").Left())
286  # DEBUG: Next was already called from application.py
287  self._mgr_mgr.AddPane(self.treetree,
288  aui.AuiPaneInfo().Name("tree_content").Caption("Test Suites").CloseButton(False).
289  LeftDockable()) # TODO: remove .CloseButton(False) when restore is fixed
290 
292  self.actionsactions.register_actions(ActionInfoCollection(_menudata, self, self.treetree))
293  # ##### File explorer panel is always created here
294  self.filemgrfilemgr = FileExplorer(self, self._controller_controller)
295  self.filemgrfilemgr.SetMinSize(wx.Size(275, 250))
296  # DEBUG: Next was already called from application.py
297  self._mgr_mgr.AddPane(self.filemgrfilemgr,
298  aui.AuiPaneInfo().Name("file_manager").
299  LeftDockable())
300 
301  mb.take_menu_bar_into_use()
302  self.CreateStatusBar(name="StatusBar")
303  self._status_bar_status_bar = self.FindWindowByName("StatusBar", self)
304  if self._status_bar_status_bar:
305  self._status_bar_status_bar.SetBackgroundColour(Colour(self.color_backgroundcolor_background))
306  self._status_bar_status_bar.SetForegroundColour(Colour(self.color_foregroundcolor_foreground))
307  # set main frame icon
308  self.SetIcons(self._image_provider_image_provider.PROGICONS)
309  # tell the manager to "commit" all the changes just made
310  self._mgr_mgr.Update()
311  # wx.CallLater(2000, RideSettingsChanged(keys=("General", ''), old='', new='').publish)
312 
313  """
314  def testToolbar(self):
315 
316  # ### More testing
317  prepend_items, append_items = [], []
318  item = aui.AuiToolBarItem()
319 
320  item.SetKind(wx.ITEM_SEPARATOR)
321  append_items.append(item)
322 
323  item = aui.AuiToolBarItem()
324  item.SetKind(wx.ITEM_NORMAL)
325  item.SetId(ID_CustomizeToolbar)
326  item.SetLabel("Customize...")
327  append_items.append(item)
328 
329  tb3 = aui.AuiToolBar(self, -1, wx.DefaultPosition, wx.DefaultSize,
330  agwStyle=aui.AUI_TB_DEFAULT_STYLE | aui.AUI_TB_OVERFLOW)
331  tb3.SetToolBitmapSize(wx.Size(16, 16))
332  tb3_bmp1 = wx.ArtProvider.GetBitmap(wx.ART_FOLDER, wx.ART_OTHER,
333  wx.Size(16, 16))
334  tb3.AddSimpleTool(ID_SampleItem + 16, "Check 1", tb3_bmp1, "Check 1",
335  aui.ITEM_CHECK)
336  tb3.AddSimpleTool(ID_SampleItem + 17, "Check 2", tb3_bmp1, "Check 2",
337  aui.ITEM_CHECK)
338  tb3.AddSimpleTool(ID_SampleItem + 18, "Check 3", tb3_bmp1, "Check 3",
339  aui.ITEM_CHECK)
340  tb3.AddSimpleTool(ID_SampleItem + 19, "Check 4", tb3_bmp1, "Check 4",
341  aui.ITEM_CHECK)
342  tb3.AddSeparator()
343  tb3.AddSimpleTool(ID_SampleItem + 20, "Radio 1", tb3_bmp1, "Radio 1",
344  aui.ITEM_RADIO)
345  tb3.AddSimpleTool(ID_SampleItem + 21, "Radio 2", tb3_bmp1, "Radio 2",
346  aui.ITEM_RADIO)
347  tb3.AddSimpleTool(ID_SampleItem + 22, "Radio 3", tb3_bmp1, "Radio 3",
348  aui.ITEM_RADIO)
349  tb3.AddSeparator()
350  tb3.AddSimpleTool(ID_SampleItem + 23, "Radio 1 (Group 2)", tb3_bmp1,
351  "Radio 1 (Group 2)", aui.ITEM_RADIO)
352  tb3.AddSimpleTool(ID_SampleItem + 24, "Radio 2 (Group 2)", tb3_bmp1,
353  "Radio 2 (Group 2)", aui.ITEM_RADIO)
354  tb3.AddSimpleTool(ID_SampleItem + 25, "Radio 3 (Group 2)", tb3_bmp1,
355  "Radio 3 (Group 2)", aui.ITEM_RADIO)
356 
357  tb3.SetCustomOverflowItems(prepend_items, append_items)
358  tb3.Realize()
359  return tb3
360  """
361 
363  return self.treetree.get_selected_datafile()
364 
366  return self.treetree.get_selected_datafile_controller()
367 
368  def OnClose(self, event):
369  if self._allowed_to_exit_allowed_to_exit():
370  try:
371  perspective = self._mgr_mgr.SavePerspective()
372  self._application_application.settings.set('AUI Perspective', perspective)
373  # deinitialize the frame manager
374  self._mgr_mgr.UnInit()
375  del self._mgr_mgr
376  except AttributeError:
377  pass
378  try:
379  nb_perspective = self.notebooknotebook.SavePerspective()
380  self._application_application.settings.set('AUI NB Perspective', nb_perspective)
381  except AttributeError:
382  pass
383  PUBLISHER.unsubscribe(self._set_label_set_label, RideTreeSelection)
384  RideClosing().publish()
385  self._task_bar_icon_task_bar_icon.Destroy()
386  self.Destroy()
387  app = wx.GetApp()
388  if app is not self._application_application:
389  # other wx app instance created unexpectedly
390  # this will cause RIDE app instance cannot invoke ExitMainLoop properly
391  self._application_application.ExitMainLoop()
392  wx.Exit()
393  else:
394  wx.CloseEvent.Veto(event)
395 
396  def OnSize(self, event):
397  if wx.VERSION >= (4, 1, 0):
398  size = self.DoGetSize()
399  else:
400  size = tuple(self.GetSize())
401  is_full_screen_mode = size == wx.DisplaySize()
402  self._application_application.settings['mainframe maximized'] = self.IsMaximized() or is_full_screen_mode
403  if not is_full_screen_mode:
404  self._application_application.settings['mainframe size'] = size
405  if wx.VERSION >= (4, 1, 0):
406  self._application_application.settings['mainframe position'] = self.DoGetPosition()
407  else:
408  self._application_application.settings['mainframe position'] = tuple(self.GetPosition())
409  event.Skip()
410 
411  def OnMove(self, event):
412  # When the window is Iconized, a move event is also raised, but we
413  # don't want to update the position in the settings file
414  if not self.IsIconized() and not self.IsMaximized():
415  if wx.VERSION >= (4, 1, 0):
416  self._application_application.settings['mainframe position'] = self.DoGetPosition()
417  else:
418  self._application_application.settings['mainframe position'] = tuple(self.GetPosition())
419  event.Skip()
420 
421  def OnMaximize(self, event):
422  self._application_application.settings['mainframe maximized'] = True
423  event.Skip()
424 
425  def OnReleasenotes(self, event):
426  pass
427 
428  def _allowed_to_exit(self):
429  if self.has_unsaved_changes():
430  ret = wx.MessageBox("There are unsaved modifications.\n"
431  "Do you want to save your changes before "
432  "exiting?", "Warning",
433  wx.ICON_WARNING | wx.CANCEL | wx.YES_NO)
434  if ret == wx.CANCEL:
435  return False
436  if ret == wx.YES:
437  self.save()
438  return True
439 
441  return self._controller_controller.is_dirty()
442 
443  def OnNewProject(self, event):
444  if not self.check_unsaved_modificationscheck_unsaved_modifications():
445  return
446  NewProjectDialog(self._controller_controller).execute()
447  self._populate_tree_populate_tree()
448 
449  def _populate_tree(self):
450  self.treetree.populate(self._controller_controller)
451  self.filemgrfilemgr.update_tree()
452 
453  def OnOpenFile(self, event):
454  if not self.filemgrfilemgr:
455  return
456  # EVT_DIRCTRL_FILEACTIVATED
457  from os.path import splitext
458  robottypes = self._application_application.settings.get('robot types', ['robot',
459  'resource',
460  'txt',
461  'tsv',
462  'html'])
463  path = self.filemgrfilemgr.GetFilePath()
464  ext = ''
465  if len(path) > 0:
466  ext = splitext(path)
467  ext = ext[1].replace('.', '')
468  # print("DEBUG: path %s ext %s" % (path, ext))
469  if len(ext) > 0 and ext in robottypes:
470  if not self.check_unsaved_modificationscheck_unsaved_modifications():
471  return
472  if self.open_suiteopen_suite(path):
473  return
474  customsourceeditor.main(path)
475 
476  def OnMenuOpenFile(self, event):
477  if not self.filemgrfilemgr:
478  return
479  # TODO: Use widgets/popupmenu tools
480  path = self.filemgrfilemgr.GetFilePath()
481  if len(path) > 0:
482  self.OnOpenFileOnOpenFile(event)
483  else:
484  path = self.filemgrfilemgr.GetPath()
485  if not self.check_unsaved_modificationscheck_unsaved_modifications():
486  return
487  self.open_suiteopen_suite(path) # It is a directory, do not edit
488  event.Skip()
489 
490  def OnOpenExternalFile(self, event):
491  if not self._current_external_dir_current_external_dir:
492  curdir = self._controller_controller.default_dir
493  else:
494  curdir = self._current_external_dir_current_external_dir
495  fdlg = wx.FileDialog(self, defaultDir=curdir, style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST)
496  if fdlg.ShowModal() == wx.ID_CANCEL:
497  return
498  path = fdlg.GetPath()
499  try:
500  self._current_external_dir_current_external_dir = os.path.dirname(path)
501  customsourceeditor.main(path)
502  except IOError:
503  wx.LogError(f"Cannot open file {path}")
504 
505  def OnOpenTestSuite(self, event):
506  if not self.check_unsaved_modificationscheck_unsaved_modifications():
507  return
508  path = RobotFilePathDialog(
509  self, self._controller_controller, self._application_application.settings).execute()
510  if path:
511  if self.open_suiteopen_suite(path):
512  return
513  customsourceeditor.main(path)
514 
516  if self.has_unsaved_changeshas_unsaved_changes():
517  ret = wx.MessageBox("There are unsaved modifications.\n"
518  "Do you want to proceed without saving?",
519  "Warning", wx.ICON_WARNING | wx.YES_NO)
520  return ret == wx.YES
521  return True
522 
523  def open_suite(self, path):
524  self._controller_controller.update_default_dir(path)
525  # self._controller.default_dir will only save dir path
526  # need to save path to self._application.workspace_path too
527  self._application_application.workspace_path = path
528  err = None
529  try:
530  err = self._controller_controller.load_datafile(path, LoadProgressObserver(self))
531  finally:
532  if isinstance(err, UserWarning):
533  # raise err # Just leave message in Parser Log
534  return False
535  self._populate_tree_populate_tree()
536  return True
537 
538  def refresh_datafile(self, item, event):
539  self.treetree.refresh_datafile(item, event)
540  if self.filemgrfilemgr:
541  self.filemgrfilemgr.ReCreateTree()
542 
543  def OnOpenDirectory(self, event):
544  if self.check_unsaved_modificationscheck_unsaved_modifications():
545  path = wx.DirSelector(message="Choose a directory containing Robot"
546  " files",
547  default_path=self._controller_controller.default_dir)
548  if path:
549  self.open_suiteopen_suite(path)
550 
551  def OnSave(self, event):
552  RideBeforeSaving().publish()
553  self.savesave()
554 
555  def OnSaveAll(self, event):
556  RideBeforeSaving().publish()
557  self.save_allsave_all()
558 
559  def save_all(self):
560  self._show_dialog_for_files_without_format_show_dialog_for_files_without_format()
561  self._controller_controller.execute(SaveAll(self.reformatreformat))
562 
563  def save(self, controller=None):
564  if controller is None:
565  controller = self.get_selected_datafile_controllerget_selected_datafile_controller()
566  if controller is not None:
567  if not controller.has_format():
568  self._show_dialog_for_files_without_format_show_dialog_for_files_without_format(controller)
569  else:
570  controller.execute(SaveFile(self.reformatreformat))
571 
572  def _show_dialog_for_files_without_format(self, controller=None):
573  files_without_format = self._controller_controller.get_files_without_format(
574  controller)
575  for f in files_without_format:
576  self._show_format_dialog_for_show_format_dialog_for(f)
577 
578  def _show_format_dialog_for(self, file_controller_without_format):
579  InitFileFormatDialog(file_controller_without_format).execute()
580 
581  def OnExit(self, event):
582  self.Close()
583 
584  def OnManagePlugins(self, event):
585  self._plugin_manager_plugin_manager.show(self._application_application.get_plugins())
586 
587  def OnViewAllTags(self, event):
588  if self._view_all_tags_dialog_view_all_tags_dialog is None:
589  self._view_all_tags_dialog_view_all_tags_dialog = ViewAllTagsDialog(self._controller_controller, self)
590  self._view_all_tags_dialog_view_all_tags_dialog.show_dialog()
591 
592  def OnSearchUnusedKeywords(self, event):
593  if self._review_dialog_review_dialog is None:
594  self._review_dialog_review_dialog = ReviewDialog(self._controller_controller, self)
595  self._review_dialog_review_dialog.show_dialog()
596 
597  def OnPreferences(self, event):
598  dlg = PreferenceEditor(self, "RIDE - Preferences",
599  self._application_application.preferences, style='tree')
600  # Changed to non-modal, original comment follows:
601  # I would prefer that this not be modal, but making it non-
602  # modal opens up a can of worms. We don't want to have to deal
603  # with settings getting changed out from under us while the
604  # dialog is open.
605  dlg.Show()
606 
607  @staticmethod
608  def OnAbout(event):
609  dlg = AboutDialog()
610  dlg.ShowModal()
611  dlg.Destroy()
612 
613  @staticmethod
614  def OnShortcutkeys(event):
615  dialog = ShortcutKeysDialog()
616  dialog.Show()
617 
618  @staticmethod
619  def OnReportaProblem(event):
620  wx.LaunchDefaultBrowser("https://github.com/robotframework/RIDE/issues"
621  "?utf8=%E2%9C%93&q=is%3Aissue+%22search"
622  "%20your%20problem%22"
623  )
624 
625  @staticmethod
626  def OnUserGuide(event):
627  wx.LaunchDefaultBrowser("http://robotframework.org/robotframework/"
628  "#user-guide")
629 
630  @staticmethod
631  def OnWiki(event):
632  wx.LaunchDefaultBrowser("https://github.com/robotframework/RIDE/wiki")
633 
634  def _has_data(self):
635  return self._controller_controller.data is not None
636 
637  def _refresh(self):
638  self._controller_controller.update_namespace()
639 
640  # This code is copied from http://wiki.wxpython.org/EnsureFrameIsOnScreen,
641  # and adapted to fit our code style.
642  def ensure_on_screen(self):
643  try:
644  display_id = wx.Display.GetFromWindow(self)
645  except NotImplementedError:
646  display_id = 0
647  if display_id == -1:
648  display_id = 0
649  geometry = wx.Display(display_id).GetGeometry()
650  position = self.GetPosition()
651  if position.x < geometry.x:
652  position.x = geometry.x
653  if position.y < geometry.y:
654  position.y = geometry.y
655  size = self.GetSize()
656  if size.width > geometry.width:
657  size.width = geometry.width
658  position.x = geometry.x
659  elif position.x + size.width > geometry.x + geometry.width:
660  position.x = geometry.x + geometry.width - size.width
661  if size.height > geometry.height:
662  size.height = geometry.height
663  position.y = geometry.y
664  elif position.y + size.height > geometry.y + geometry.height:
665  position.y = geometry.y + geometry.height - size.height
666  self.SetPosition(position)
667  self.SetSize(size)
668 
669  # DEBUG just some testing
670  """
671  def CreateSizeReportCtrl(self, width=80, height=80):
672 
673  ctrl = SizeReportCtrl(self, -1, wx.DefaultPosition, wx.Size(width, height), self._mgr)
674  return ctrl
675  """
676 
677  def show_confirm_reload_dlg(self, event):
678  msg = ['Workspace modifications detected on the file system.',
679  'Do you want to reload the workspace?',
680  'Answering <No> will overwrite the changes on disk.']
681  if self._controller_controller.is_dirty():
682  msg.insert(2, 'Answering <Yes> will discard unsaved changes.')
683  ret = wx.MessageBox('\n'.join(msg), 'Files Changed On Disk',
684  style=wx.YES_NO | wx.ICON_WARNING)
685  confirmed = ret == wx.YES
686  if confirmed:
687  # workspace_path should update after open directory/suite
688  # There're two scenarios:
689  # 1. path is a directory
690  # 2. path is a suite file
691  new_path = RideFSWatcherHandler.get_workspace_new_path()
692  if new_path and os.path.exists(new_path):
693  wx.CallAfter(self.open_suiteopen_suite, new_path)
694  else:
695  # in case workspace is totally removed
696  # ask user to open new directory
697  # TODO add some notification msg to users
698  wx.CallAfter(self.OnOpenDirectoryOnOpenDirectory, event)
699  else:
700  for _ in self._controller_controller.datafiles:
701  if _.has_been_modified_on_disk() or _.has_been_removed_from_disk():
702  if not os.path.exists(_.directory):
703  # sub folder is removed, create new one before saving
704  os.makedirs(_.directory)
705  _.mark_dirty()
706  self.save_allsave_all()
707 """
708  def OnSettingsChanged(self, message):
709  #TODO: change to doc Redraw the colors if the color settings are modified
710  # section, setting = data.keys
711  #print(f"DEBUG: OnSettingsChanged enter {repr(message)}")
712  if isinstance(message, _Section):
713  ndata= message
714  # print(f"DEBUG: OnSettingsChanged in Section {type(ndata)}")
715  for key, value in message:
716  # print(f"DEBUG: OnSettingsChanged in key {key} value {value}")
717  background = message.get_without_default('background')
718  foreground = message.get_without_default('foreground')
719  # print(f"DEBUG: OnSettings section: {message._is_section('General')} background {background}"
720  f" foreground {foreground}")
721  if message._is_section('General'):
722  _settings = RideSettings()
723  _general_settings = _settings['General']
724  children = self.GetChildren()
725  for child in children:
726  child.SetBackgroundColour(Colour(_general_settings['background']))
727  child.SetOwnBackgroundColour(Colour(_general_settings['background']))
728  child.SetForegroundColour(Colour(_general_settings['foreground']))
729  child.SetOwnForegroundColour(Colour(_general_settings['foreground']))
730  font = child.GetFont()
731  font.SetFaceName(_general_settings['font face'])
732  font.SetPointSize(_general_settings['font size'])
733  child.SetFont(font)
734  child.Refresh(True)
735  # print(f"DEBUG: OnSettingsChanged child {type(child)}")
736  # print(f"DEBUG: OnSettingsChanged not General")
737 """
738 
739 
740 # Code moved from actiontriggers
741 class ToolBar(aui.AuiToolBar):
742 
743  def __init__(self, frame):
744  aui.AuiToolBar.__init__(self, frame)
745  # prepare a few custom overflow elements for the toolbars' overflow buttons
746  prepend_items, append_items = [], []
747  item = aui.AuiToolBarItem()
748 
749  item.SetKind(wx.ITEM_SEPARATOR)
750  append_items.append(item)
751 
752  item = aui.AuiToolBarItem()
753  item.SetKind(wx.ITEM_NORMAL)
754  item.SetId(ID_CustomizeToolbar)
755  item.SetLabel("Customize...")
756  append_items.append(item)
757 
758  self._frame_frame = frame
759  # DEBUG If we attach to frame it won't be detachable, and overlaps
760  # If self, buttons are not shown
761  self.tbtb = aui.AuiToolBar(self, -1, wx.DefaultPosition,
762  wx.DefaultSize,
763  agwStyle=aui.AUI_TB_DEFAULT_STYLE | aui.AUI_TB_OVERFLOW)
764  self.tbtb.SetToolBitmapSize(wx.Size(16, 16))
765  self._buttons_buttons = []
766  self._search_handlers_search_handlers = {}
767  self._current_description_current_description = None
768  self.SetMinSize(wx.Size(100, 60))
769  # self.tb.SetBackgroundColour(Colour(self._frame.color_background))
770  # self.tb.SetForegroundColour(Colour(self._frame.color_foreground))
771  self.tbtb.SetCustomOverflowItems(prepend_items, append_items)
772  self.tbtb.Realize()
773 
774  def register(self, action):
775  if action.has_icon():
776  button = self._get_existing_button_get_existing_button(action)
777  if not button:
778  button = self._create_button_create_button(action)
779  button.register(action)
780 
781  def _get_existing_button(self, action):
782  for button in self._buttons_buttons:
783  if button.icon == action.icon:
784  return button
785  return None
786 
787  def enabled_status_changed(self, id, action):
788  self.EnableTool(id, action.is_active())
789 
790  def _create_button(self, action):
791  button = ToolBarButton(self._frame_frame, self, action)
792  name = self._format_button_tooltip_format_button_tooltip(action)
793  self.AddTool(button.id, name, action.icon, wx.NullBitmap,
794  wx.ITEM_NORMAL, name, action.doc)
795  self.Realize()
796  self._buttons_buttons.append(button)
797  return button
798 
799  def _format_button_tooltip(self, action):
800  tooltip = action.name.replace('&', '')
801  if action.shortcut and action.shortcut.value:
802  tooltip = '%s (%s)' % (tooltip, action.shortcut.value)
803  return tooltip
804 
805  def remove_toolbar_button(self, button):
806  self._buttons_buttons.remove(button)
807  self.DeleteTool(button.id)
808  self.Realize()
809 
810  def register_search_handler(self, description, handler, icon,
811  default=False):
812  if default:
813  self._current_description_current_description = description
814  self._search_handlers_search_handlers[description] = _RideSearchMenuItem(handler, icon)
815 
816 
818 
819  def __init__(self, aui_mgr, menubar, toolbar, shortcut_registry):
820  self._aui_mgr_aui_mgr = aui_mgr
821  self._menubar_menubar = menubar
822  self._toolbar_toolbar = toolbar
823  self._shortcut_registry_shortcut_registry = shortcut_registry
824  self._tools_items_tools_items = dict()
825 
826  def register_action(self, action_info, update_aui=True):
827  menubar_can_be_registered = True
828  action = ActionFactory(action_info)
829  self._shortcut_registry_shortcut_registry.register(action)
830  if hasattr(action_info, "menu_name"):
831  if action_info.menu_name == "Tools":
832  self._tools_items_tools_items[action_info.position] = action
833  menubar_can_be_registered = False
834  if menubar_can_be_registered:
835  self._menubar_menubar.register(action)
836  self._toolbar_toolbar.register(action)
837  if update_aui:
838  # tell the manager to "commit" all the changes just made
839  self._aui_mgr_aui_mgr.Update()
840  return action
841 
842  def register_tools(self):
843  separator_action = ActionFactory(SeparatorInfo("Tools"))
844  add_separator_after = ["stop test run", "search unused keywords",
845  "preview", "view ride log"]
846  #for key in sorted(self._tools_items.iterkeys()):
847  # print("DEBUG: at register_tools, tools: %s" % self._tools_items)
848  for key in sorted(self._tools_items_tools_items.keys()): #DEBUG Python3
849  self._menubar_menubar.register(self._tools_items_tools_items[key])
850  # print("DEBUG: key=%s name=%s" % (key, self._tools_items[key].name.lower()))
851  if self._tools_items_tools_items[key].name.lower() in add_separator_after:
852  self._menubar_menubar.register(separator_action)
853 
854  def register_actions(self, actions):
855  for action in actions:
856  if not isinstance(action, SeparatorInfo): # DEBUG
857  # print("DEBUG: action=%s" % action.name)
858  self.register_actionregister_action(action, update_aui=False)
859  # tell the manager to "commit" all the changes just made
860  self._aui_mgr_aui_mgr.Update()
861 
862  def register_shortcut(self, action_info):
863  action = ActionFactory(action_info)
864  self._shortcut_registry_shortcut_registry.register(action)
865  return action
866 
867 
868 class AboutDialog(RIDEDialog):
869 
870  def __init__(self):
871  RIDEDialog.__init__(self, title='RIDE')
872  # set Left to Right direction (while we don't have localization)
873  self.SetLayoutDirection(wx.Layout_LeftToRight)
874  sizer = wx.BoxSizer(wx.VERTICAL)
875  sizer.Add(HtmlWindow(self, (650, 200), ABOUT_RIDE), 1, flag=wx.EXPAND)
876  self.SetSizerAndFit(sizer)
877 
878  def OnKey(self, *args):
879  pass
880 
881 
882 class ShortcutKeysDialog(RIDEDialog):
883 
884  def __init__(self):
885  RIDEDialog.__init__(self, title="Shortcut keys for RIDE")
886  # set Left to Right direction (while we don't have localization)
887  self.SetLayoutDirection(wx.Layout_LeftToRight)
888  sizer = wx.BoxSizer(wx.HORIZONTAL)
889  sizer.Add(HtmlWindow(self, (350, 400),
890  self._get_platform_specific_shortcut_keys_get_platform_specific_shortcut_keys()), 1,
891  flag=wx.EXPAND)
892  self.SetSizerAndFit(sizer)
893 
894  def OnKey(self, *args):
895  pass
896 
898  return localize_shortcuts(SHORTCUT_KEYS)
899 
900 
901 class RIDETaskBarIcon(TaskBarIcon):
902 
903  def __init__(self, img_provider):
904  TaskBarIcon.__init__(self, TBI_DOCK)
905  self._img_provider_img_provider = img_provider
906  # if IS_MAC:
907  # # only use in mac to display RIDE app icon in dock
908  self.SetIcon(wx.Icon(self._img_provider_img_provider.RIDE_ICON), "RIDE")
Used to create separators to menus.
Definition: actioninfo.py:262
A dialog for showing the preference panels.
Definition: editor.py:45
Sent before files are going to be saved.
Definition: messages.py:254
Sent when user selects Quit from File menu or via shortcut.
Definition: messages.py:282
def __init__(self, aui_mgr, menubar, toolbar, shortcut_registry)
Definition: mainframe.py:819
def register_shortcut(self, action_info)
Definition: mainframe.py:862
def register_action(self, action_info, update_aui=True)
Definition: mainframe.py:826
def __init__(self, img_provider)
Definition: mainframe.py:903
def OnOpenFile(self, event)
Definition: mainframe.py:453
def _show_format_dialog_for(self, file_controller_without_format)
Definition: mainframe.py:578
def OnSearchUnusedKeywords(self, event)
Definition: mainframe.py:592
def refresh_datafile(self, item, event)
Definition: mainframe.py:538
def OnOpenDirectory(self, event)
Definition: mainframe.py:543
def OnReleasenotes(self, event)
Definition: mainframe.py:425
def __init__(self, application, controller)
Definition: mainframe.py:146
filemgr
self._mgr.GetPane(self.tree).DestroyOnClose() TreePlugin will manage showing the Tree
Definition: mainframe.py:294
def OnOpenTestSuite(self, event)
Definition: mainframe.py:505
def save(self, controller=None)
Definition: mainframe.py:563
def OnManagePlugins(self, event)
Definition: mainframe.py:584
def _show_validation_error(self, message)
Definition: mainframe.py:217
def OnMenuOpenFile(self, event)
Definition: mainframe.py:476
def OnSaveAll(self, event)
Definition: mainframe.py:555
def OnOpenExternalFile(self, event)
Definition: mainframe.py:490
def _create_title(self, message)
Definition: mainframe.py:208
def _show_dialog_for_files_without_format(self, controller=None)
Definition: mainframe.py:572
def OnMaximize(self, event)
Definition: mainframe.py:421
def OnViewAllTags(self, event)
Definition: mainframe.py:587
def _set_label(self, message)
Definition: mainframe.py:205
def get_selected_datafile_controller(self)
Definition: mainframe.py:365
def show_confirm_reload_dlg(self, event)
Definition: mainframe.py:677
def OnNewProject(self, event)
Definition: mainframe.py:443
def OnPreferences(self, event)
Definition: mainframe.py:597
def _show_modification_prevented_error(self, message)
Definition: mainframe.py:220
def _format_button_tooltip(self, action)
Definition: mainframe.py:799
def _get_existing_button(self, action)
Definition: mainframe.py:781
def enabled_status_changed(self, id, action)
Definition: mainframe.py:787
def __init__(self, frame)
Definition: mainframe.py:743
def register_search_handler(self, description, handler, icon, default=False)
Definition: mainframe.py:811
def register(self, action)
Definition: mainframe.py:774
def _create_button(self, action)
Definition: mainframe.py:790
def remove_toolbar_button(self, button)
Definition: mainframe.py:805
def ActionFactory(action_info)
Definition: action.py:17
def ActionInfoCollection(data, event_handler, container=None)
Parses the data into a list of ActionInfo and SeparatorInfo objects.
Definition: actioninfo.py:106
def localize_shortcuts(string)
Definition: shortcut.py:57