19 import wx.lib.agw.aui
as aui
21 from wx.adv
import TaskBarIcon, TBI_DOCK
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
50 !&New Project | Create a new top level suite | Ctrlcmd-N | ART_NEW
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
56 !&Save | Save selected datafile | Ctrlcmd-S | ART_FILE_SAVE
57 !Save &All | Save all changes | Ctrlcmd-Shift-S | ART_FILE_SAVE_AS
59 !E&xit | Exit RIDE | Ctrlcmd-Q
62 !Search Unused Keywords | | | | POSITION-54
63 !Manage Plugins | | | | POSITION-81
64 !View All Tags | | F7 | | POSITION-82
65 !Preferences | | | | POSITION-99
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
76 ID_CustomizeToolbar = wx.ID_HIGHEST + 1
77 ID_SampleItem = ID_CustomizeToolbar + 1
82 # -- DEBUG some testing
83 # -- SizeReportCtrl --
84 # (a utility control that always reports it's client size)
87 class SizeReportCtrl(wx.Control):
89 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, mgr=None):
91 wx.Control.__init__(self, parent, id, pos, size, style=wx.NO_BORDER)
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)
98 def OnPaint(self, event):
100 dc = wx.PaintDC(self)
101 size = self.GetClientSize()
103 s = "Size: %d x %d" % (size.x, size.y)
105 dc.SetFont(wx.NORMAL_FONT)
106 w, height = dc.GetTextExtent(s)
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)
118 pi = self._mgr.GetPane(self)
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))
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))
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))
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))
136 def OnEraseBackground(self, event):
139 def OnSize(self, event):
147 size = application.settings.get(
'mainframe size', (1100, 700))
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)
154 self.SetLayoutDirection(wx.Layout_LeftToRight)
157 self.
_mgr_mgr = aui.AuiManager()
160 self.
_mgr_mgr.SetManagedWindow(self)
162 self.SetMinSize(wx.Size(400, 300))
165 if application.settings.get(
'mainframe maximized',
False):
170 self.
reformatreformat = application.settings.get(
'reformat',
False)
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)
191 wx.CallAfter(self.
actionsactions.register_tools)
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),
203 PUBLISHER.subscribe(listener, topic)
212 title +=
' - ' + item.datafile.name
213 if not item.is_modifiable():
214 title +=
' (READ ONLY)'
218 wx.MessageBox(message.message,
'Validation Error', style=wx.ICON_ERROR)
221 wx.MessageBox(
"\"%s\" is read only" % message.controller.datafile_controller.filename,
"Modification prevented",
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
239 aui.AuiPaneInfo().Name(
"notebook_editors").
240 CenterPane().PaneBorder(
False))
260 self.
toolbartoolbar.SetMinSize(wx.Size(100, 60))
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").
272 tb3 = self.testToolbar()
274 self._mgr.AddPane(tb3,
275 aui.AuiPaneInfo().Name("tb3").Caption("Toolbar 3").
276 ToolbarPane().Top().Row(1).Position(1))
283 self.
treetree.SetMinSize(wx.Size(275, 250))
287 self.
_mgr_mgr.AddPane(self.
treetree,
288 aui.AuiPaneInfo().Name(
"tree_content").Caption(
"Test Suites").CloseButton(
False).
295 self.
filemgrfilemgr.SetMinSize(wx.Size(275, 250))
298 aui.AuiPaneInfo().Name(
"file_manager").
301 mb.take_menu_bar_into_use()
302 self.CreateStatusBar(name=
"StatusBar")
303 self.
_status_bar_status_bar = self.FindWindowByName(
"StatusBar", self)
310 self.
_mgr_mgr.Update()
314 def testToolbar(self):
317 prepend_items, append_items = [], []
318 item = aui.AuiToolBarItem()
320 item.SetKind(wx.ITEM_SEPARATOR)
321 append_items.append(item)
323 item = aui.AuiToolBarItem()
324 item.SetKind(wx.ITEM_NORMAL)
325 item.SetId(ID_CustomizeToolbar)
326 item.SetLabel("Customize...")
327 append_items.append(item)
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,
334 tb3.AddSimpleTool(ID_SampleItem + 16, "Check 1", tb3_bmp1, "Check 1",
336 tb3.AddSimpleTool(ID_SampleItem + 17, "Check 2", tb3_bmp1, "Check 2",
338 tb3.AddSimpleTool(ID_SampleItem + 18, "Check 3", tb3_bmp1, "Check 3",
340 tb3.AddSimpleTool(ID_SampleItem + 19, "Check 4", tb3_bmp1, "Check 4",
343 tb3.AddSimpleTool(ID_SampleItem + 20, "Radio 1", tb3_bmp1, "Radio 1",
345 tb3.AddSimpleTool(ID_SampleItem + 21, "Radio 2", tb3_bmp1, "Radio 2",
347 tb3.AddSimpleTool(ID_SampleItem + 22, "Radio 3", tb3_bmp1, "Radio 3",
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)
357 tb3.SetCustomOverflowItems(prepend_items, append_items)
371 perspective = self.
_mgr_mgr.SavePerspective()
372 self.
_application_application.settings.set(
'AUI Perspective', perspective)
374 self.
_mgr_mgr.UnInit()
376 except AttributeError:
379 nb_perspective = self.
notebooknotebook.SavePerspective()
380 self.
_application_application.settings.set(
'AUI NB Perspective', nb_perspective)
381 except AttributeError:
383 PUBLISHER.unsubscribe(self.
_set_label_set_label, RideTreeSelection)
394 wx.CloseEvent.Veto(event)
397 if wx.VERSION >= (4, 1, 0):
398 size = self.DoGetSize()
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()
408 self.
_application_application.settings[
'mainframe position'] = tuple(self.GetPosition())
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()
418 self.
_application_application.settings[
'mainframe position'] = tuple(self.GetPosition())
422 self.
_application_application.settings[
'mainframe maximized'] =
True
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)
451 self.
filemgrfilemgr.update_tree()
457 from os.path
import splitext
458 robottypes = self.
_application_application.settings.get(
'robot types', [
'robot',
463 path = self.
filemgrfilemgr.GetFilePath()
467 ext = ext[1].replace(
'.',
'')
469 if len(ext) > 0
and ext
in robottypes:
474 customsourceeditor.main(path)
480 path = self.
filemgrfilemgr.GetFilePath()
484 path = self.
filemgrfilemgr.GetPath()
495 fdlg = wx.FileDialog(self, defaultDir=curdir, style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST)
496 if fdlg.ShowModal() == wx.ID_CANCEL:
498 path = fdlg.GetPath()
501 customsourceeditor.main(path)
503 wx.LogError(f
"Cannot open file {path}")
513 customsourceeditor.main(path)
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)
524 self.
_controller_controller.update_default_dir(path)
532 if isinstance(err, UserWarning):
541 self.
filemgrfilemgr.ReCreateTree()
545 path = wx.DirSelector(message=
"Choose a directory containing Robot"
547 default_path=self.
_controller_controller.default_dir)
563 def save(self, controller=None):
564 if controller
is None:
566 if controller
is not None:
567 if not controller.has_format():
573 files_without_format = self.
_controller_controller.get_files_without_format(
575 for f
in files_without_format:
599 self.
_application_application.preferences, style=
'tree')
620 wx.LaunchDefaultBrowser(
"https://github.com/robotframework/RIDE/issues"
621 "?utf8=%E2%9C%93&q=is%3Aissue+%22search"
622 "%20your%20problem%22"
627 wx.LaunchDefaultBrowser(
"http://robotframework.org/robotframework/"
632 wx.LaunchDefaultBrowser(
"https://github.com/robotframework/RIDE/wiki")
635 return self.
_controller_controller.data
is not None
644 display_id = wx.Display.GetFromWindow(self)
645 except NotImplementedError:
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)
671 def CreateSizeReportCtrl(self, width=80, height=80):
673 ctrl = SizeReportCtrl(self, -1, wx.DefaultPosition, wx.Size(width, height), self._mgr)
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.']
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
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)
701 if _.has_been_modified_on_disk()
or _.has_been_removed_from_disk():
702 if not os.path.exists(_.directory):
704 os.makedirs(_.directory)
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):
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'])
735 # print(f"DEBUG: OnSettingsChanged child {type(child)}")
736 # print(f"DEBUG: OnSettingsChanged not General")
744 aui.AuiToolBar.__init__(self, frame)
746 prepend_items, append_items = [], []
747 item = aui.AuiToolBarItem()
749 item.SetKind(wx.ITEM_SEPARATOR)
750 append_items.append(item)
752 item = aui.AuiToolBarItem()
753 item.SetKind(wx.ITEM_NORMAL)
754 item.SetId(ID_CustomizeToolbar)
755 item.SetLabel(
"Customize...")
756 append_items.append(item)
761 self.
tbtb = aui.AuiToolBar(self, -1, wx.DefaultPosition,
763 agwStyle=aui.AUI_TB_DEFAULT_STYLE | aui.AUI_TB_OVERFLOW)
764 self.
tbtb.SetToolBitmapSize(wx.Size(16, 16))
768 self.SetMinSize(wx.Size(100, 60))
771 self.
tbtb.SetCustomOverflowItems(prepend_items, append_items)
775 if action.has_icon():
779 button.register(action)
782 for button
in self.
_buttons_buttons:
783 if button.icon == action.icon:
788 self.EnableTool(id, action.is_active())
793 self.AddTool(button.id, name, action.icon, wx.NullBitmap,
794 wx.ITEM_NORMAL, name, action.doc)
796 self.
_buttons_buttons.append(button)
800 tooltip = action.name.replace(
'&',
'')
801 if action.shortcut
and action.shortcut.value:
802 tooltip =
'%s (%s)' % (tooltip, action.shortcut.value)
806 self.
_buttons_buttons.remove(button)
807 self.DeleteTool(button.id)
819 def __init__(self, aui_mgr, menubar, toolbar, shortcut_registry):
827 menubar_can_be_registered =
True
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)
844 add_separator_after = [
"stop test run",
"search unused keywords",
845 "preview",
"view ride log"]
848 for key
in sorted(self.
_tools_items_tools_items.keys()):
851 if self.
_tools_items_tools_items[key].name.lower()
in add_separator_after:
852 self.
_menubar_menubar.register(separator_action)
855 for action
in actions:
856 if not isinstance(action, SeparatorInfo):
871 RIDEDialog.__init__(self, title=
'RIDE')
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)
882 class ShortcutKeysDialog(RIDEDialog):
885 RIDEDialog.__init__(self, title=
"Shortcut keys for RIDE")
887 self.SetLayoutDirection(wx.Layout_LeftToRight)
888 sizer = wx.BoxSizer(wx.HORIZONTAL)
889 sizer.Add(HtmlWindow(self, (350, 400),
892 self.SetSizerAndFit(sizer)
904 TaskBarIcon.__init__(self, TBI_DOCK)
908 self.SetIcon(wx.Icon(self.
_img_provider_img_provider.RIDE_ICON),
"RIDE")
Used to create separators to menus.
A dialog for showing the preference panels.
Sent before files are going to be saved.
Sent when user selects Quit from File menu or via shortcut.
def register_actions(self, actions)
def __init__(self, aui_mgr, menubar, toolbar, shortcut_registry)
def register_shortcut(self, action_info)
def register_action(self, action_info, update_aui=True)
def __init__(self, img_provider)
def OnOpenFile(self, event)
def _show_format_dialog_for(self, file_controller_without_format)
def OnSearchUnusedKeywords(self, event)
def refresh_datafile(self, item, event)
def OnOpenDirectory(self, event)
def OnReleasenotes(self, event)
def __init__(self, application, controller)
filemgr
self._mgr.GetPane(self.tree).DestroyOnClose() TreePlugin will manage showing the Tree
def ensure_on_screen(self)
def OnOpenTestSuite(self, event)
def save(self, controller=None)
def OnShortcutkeys(event)
def OnManagePlugins(self, event)
def _show_validation_error(self, message)
def OnMenuOpenFile(self, event)
def OnSaveAll(self, event)
def OnOpenExternalFile(self, event)
def _create_title(self, message)
def _show_dialog_for_files_without_format(self, controller=None)
def OnMaximize(self, event)
def _allowed_to_exit(self)
def has_unsaved_changes(self)
def OnViewAllTags(self, event)
def _subscribe_messages(self)
def _set_label(self, message)
def check_unsaved_modifications(self)
def get_selected_datafile(self)
def get_selected_datafile_controller(self)
def show_confirm_reload_dlg(self, event)
def OnNewProject(self, event)
def OnPreferences(self, event)
def open_suite(self, path)
def _show_modification_prevented_error(self, message)
def OnReportaProblem(event)
def _get_platform_specific_shortcut_keys(self)
def _format_button_tooltip(self, action)
def _get_existing_button(self, action)
def enabled_status_changed(self, id, action)
def __init__(self, frame)
def register_search_handler(self, description, handler, icon, default=False)
def register(self, action)
def _create_button(self, action)
def remove_toolbar_button(self, button)
def ActionFactory(action_info)
def ActionInfoCollection(data, event_handler, container=None)
Parses the data into a list of ActionInfo and SeparatorInfo objects.
def localize_shortcuts(string)