Beispiel #1
0
class KeywordEditor(with_metaclass(classmaker(), GridEditor,
                                   RideEventHandler)):
    _no_cell = (-1, -1)
    _popup_menu_shown = False
    dirty = property(lambda self: self._controller.dirty)
    update_value = lambda *args: None
    _popup_items = [
        'Create Keyword', 'Extract Keyword', 'Extract Variable',
        'Rename Keyword', 'Find Where Used', 'JSON Editor\tCtrl-Shift-J',
        '---', 'Make Variable\tCtrl-1', 'Make List Variable\tCtrl-2',
        'Make Dict Variable\tCtrl-5', '---', 'Go to Definition\tCtrl-B', '---'
    ] + GridEditor._popup_items

    def __init__(self, parent, controller, tree):
        GridEditor.__init__(self, parent,
                            len(controller.steps) + 5,
                            max((controller.max_columns + 1), 5),
                            parent.plugin._grid_popup_creator)

        self.settings = parent.plugin.global_settings['Grid']
        self._parent = parent
        self._plugin = parent.plugin
        self._cell_selected = False
        self._colorizer = Colorizer(self, controller)
        self._controller = controller
        self._configure_grid()
        self._updating_namespace = False
        self._controller.datafile_controller.register_for_namespace_updates(
            self._namespace_updated)
        self._tooltips = GridToolTips(self)
        self._marked_cell = None
        self._make_bindings()
        self._write_steps(self._controller)
        self.autosize()
        self._tree = tree
        self._has_been_clicked = False
        self._counter = 0  # Workaround for double delete actions
        self._dcells = None  # Workaround for double delete actions
        self._icells = None  # Workaround for double insert actions
        PUBLISHER.subscribe(self._before_saving, RideBeforeSaving)
        PUBLISHER.subscribe(self._data_changed, RideItemStepsChanged)
        PUBLISHER.subscribe(self.OnSettingsChanged, RideSettingsChanged)
        PUBLISHER.subscribe(self._resize_grid, RideSaved)

    def _namespace_updated(self):
        if not self._updating_namespace:
            self._updating_namespace = True
            # See following issue for history of the next line:
            # http://code.google.com/p/robotframework-ride/issues/detail?id=1108
            wx.CallAfter(wx.CallLater, 200,
                         self._update_based_on_namespace_change)

    def _update_based_on_namespace_change(self):
        try:
            self._colorize_grid()
        finally:
            self._updating_namespace = False

    @requires_focus
    def _resize_grid(self, event=None):
        if self.settings.get("auto size cols", True):
            self.AutoSizeColumns(False)
        if self.settings.get("word wrap", True):
            self.AutoSizeRows(False)

    def _set_cells(self):
        col_size = self.settings.get("col size", 150)
        max_col_size = self.settings.get("max col size", 450)
        auto_col_size = self.settings.get("auto size cols", False)
        word_wrap = self.settings.get("word wrap", True)

        self.SetDefaultRenderer(
            CellRenderer(col_size, max_col_size, auto_col_size, word_wrap))
        self.SetRowLabelSize(wx.grid.GRID_AUTOSIZE)
        self.SetColLabelSize(0)

        if auto_col_size:
            self.SetDefaultColSize(wx.grid.GRID_AUTOSIZE,
                                   resizeExistingCols=True)
        else:
            self.SetDefaultColSize(col_size, resizeExistingCols=True)
            self.SetColMinimalAcceptableWidth(col_size)

        if auto_col_size:
            self.Bind(grid.EVT_GRID_CMD_COL_SIZE, self.OnCellColSizeChanged)
        else:
            self.Unbind(grid.EVT_GRID_CMD_COL_SIZE)

        if word_wrap:
            self.SetDefaultRowSize(wx.grid.GRID_AUTOSIZE)
        self.SetDefaultCellOverflow(False)  # DEBUG

    def _configure_grid(self):
        self._set_cells()
        self.SetDefaultEditor(
            ContentAssistCellEditor(self._plugin, self._controller))
        self._set_fonts()

    def _set_fonts(self, update_cells=False):
        font_size = self.settings.get('font size', _DEFAULT_FONT_SIZE)
        font_family = wx.FONTFAMILY_MODERN if self.settings['fixed font'] \
            else wx.FONTFAMILY_DEFAULT
        font_face = self.settings.get('font face', None)
        if font_face is None:
            font = wx.Font(font_size, font_family, wx.FONTSTYLE_NORMAL,
                           wx.FONTWEIGHT_NORMAL)
            self.settings.set('font face', font.GetFaceName())
        else:
            font = wx.Font(font_size, font_family, wx.FONTSTYLE_NORMAL,
                           wx.FONTWEIGHT_NORMAL, False, font_face)
        self.SetDefaultCellFont(font)
        for row in range(self.NumberRows):
            for col in range(self.NumberCols):
                self.SetCellFont(row, col, font)
                self.ForceRefresh()

    def _make_bindings(self):
        self.Bind(grid.EVT_GRID_EDITOR_SHOWN, self.OnEditor)
        self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
        self.Bind(wx.EVT_CHAR, self.OnChar)
        self.Bind(wx.EVT_KEY_UP, self.OnKeyUp)
        self.Bind(grid.EVT_GRID_CELL_LEFT_CLICK, self.OnCellLeftClick)
        self.Bind(grid.EVT_GRID_CELL_LEFT_DCLICK, self.OnCellLeftDClick)
        self.Bind(grid.EVT_GRID_LABEL_RIGHT_CLICK, self.OnLabelRightClick)
        self.Bind(grid.EVT_GRID_LABEL_LEFT_CLICK, self.OnLabelLeftClick)
        self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)

    def get_tooltip_content(self):
        _iscelleditcontrolshown = self.IsCellEditControlShown()
        if _iscelleditcontrolshown or self._popup_menu_shown:
            return ''
        cell = self.cell_under_cursor
        cell_info = self._controller.get_cell_info(cell.Row, cell.Col)
        return TipMessage(cell_info)

    def OnSettingsChanged(self, data):
        '''Redraw the colors if the color settings are modified'''
        section, setting = data.keys
        if section == 'Grid':
            if 'font' in setting:
                self._set_fonts(update_cells=True)
            elif ('col size' in setting or 'max col size' in setting
                  or 'auto size cols' in setting or 'word wrap' in setting):
                self._set_cells()
            self.autosize()
            self._colorize_grid()

    def OnSelectCell(self, event):
        self._cell_selected = True
        GridEditor.OnSelectCell(self, event)
        self._colorize_grid()
        event.Skip()

    def OnKillFocus(self, event):
        self._tooltips.hide()
        self._hide_link_if_necessary()
        self.save()
        event.Skip()

    def _execute(self, command):
        return self._controller.execute(command)

    def _toggle_underlined(self, cell):
        font = self.GetCellFont(cell.Row, cell.Col)
        font.SetUnderlined(not font.Underlined)
        self.SetCellFont(cell.Row, cell.Col, font)
        self.Refresh()

    def OnLabelRightClick(self, event):
        if event.Col == -1:
            self._row_label_right_click(event)
        else:
            self._col_label_right_click(event)

    def _row_label_right_click(self, event):
        selected_row = event.GetRow()
        selected_rows = self.selection.rows()
        if selected_row not in selected_rows:
            self.SelectRow(selected_row, addToSelected=False)
            self.SetGridCursor(event.Row, 0)
        popupitems = [
            'Insert Rows\tCtrl-I', 'Delete Rows\tCtrl-D',
            'Comment Rows\tCtrl-3', 'Uncomment Rows\tCtrl-4',
            'Move Rows Up\tAlt-Up', 'Move Rows Down\tAlt-Down'
        ]
        PopupMenu(self, PopupMenuItems(self, popupitems))
        event.Skip()

    def _col_label_right_click(self, event):
        pass

    def OnLabelLeftClick(self, event):
        if event.Col == -1:
            self._row_label_left_click(event)
        else:
            self._col_label_left_click(event)

    def _row_label_left_click(self, event):
        if event.ShiftDown() or event.ControlDown():
            self.ClearSelection()
            cursor_row = self.GetGridCursorRow()
            event_row = event.Row
            start, end = (cursor_row, event_row) \
                if cursor_row < event_row else (event_row, cursor_row)
            for row in range(start, end + 1):
                self.SelectRow(row, addToSelected=True)
        else:
            self.SelectRow(event.Row, addToSelected=False)
            self.SetGridCursor(event.Row, 0)

    def _col_label_left_click(self, event):
        pass

    def OnInsertRows(self, event):
        self._execute(AddRows(self.selection.rows()))
        self.ClearSelection()
        self._resize_grid()
        self._skip_except_on_mac(event)

    def _skip_except_on_mac(self, event):  # TODO Do we still need this?
        if event is not None and not IS_MAC:
            # print("DEBUG skip!")
            event.Skip()

    def OnInsertCells(self, event=None):
        # TODO remove below workaround for double actions
        if self._counter == 1:
            if self._icells == (self.selection.topleft,
                                self.selection.bottomright):
                self._counter = 0
                self._icells = None
                return
        else:
            self._counter = 1

        self._icells = (self.selection.topleft, self.selection.bottomright)
        self._execute(
            InsertCells(self.selection.topleft, self.selection.bottomright))
        self._resize_grid()
        self._skip_except_on_mac(event)

    def OnDeleteCells(self, event=None):
        # TODO remove below workaround for double actions
        if self._counter == 1:
            if self._dcells == (self.selection.topleft,
                                self.selection.bottomright):
                self._counter = 0
                self._dcells = None
                return
        else:
            self._counter = 1

        self._dcells = (self.selection.topleft, self.selection.bottomright)
        self._execute(
            DeleteCells(self.selection.topleft, self.selection.bottomright))
        self._resize_grid()
        self._skip_except_on_mac(event)

    # DEBUG @requires_focus
    def OnCommentRows(self, event=None):
        self._execute(CommentRows(self.selection.rows()))
        self._resize_grid()
        self._skip_except_on_mac(event)

    # DEBUG @requires_focus
    def OnUncommentRows(self, event=None):
        self._execute(UncommentRows(self.selection.rows()))
        self._resize_grid()
        self._skip_except_on_mac(event)

    def OnMoveRowsUp(self, event=None):
        self._row_move(MoveRowsUp, -1)

    def OnMoveRowsDown(self, event=None):
        self._row_move(MoveRowsDown, 1)

    def _row_move(self, command, change):
        # Workaround for double actions, see issue #2048
        if self._counter == 1:
            if IS_MAC:
                row = self.GetGridCursorRow() + change
                col = self.GetGridCursorCol()
                if row >= 0:
                    self.SetGridCursor(row, col)
                self._counter = 0
                return
        else:
            self._counter += 1
        rows = self.selection.rows()
        if self._execute(command(rows)):
            wx.CallAfter(self._select_rows, [r + change for r in rows])
        self._resize_grid()

    def _select_rows(self, rows):
        self.ClearSelection()
        for r in rows:
            self.SelectRow(r, True)

    def OnMotion(self, event):
        pass

    def _before_saving(self, data):
        if self.IsCellEditControlShown():
            # Fix: cannot save modifications in edit mode
            # Exit edit mode before saving
            self.HideCellEditControl()
            self.SaveEditControlValue()
            self.SetFocus()

    def _data_changed(self, data):
        if self._controller == data.item:
            self._write_steps(data.item)

    def _write_steps(self, controller):
        data = []
        self._write_headers(controller)
        for step in controller.steps:
            data.append(self._format_comments(step.as_list()))
        self.ClearGrid()
        self._write_data(data, update_history=False)
        self._colorize_grid()

    def _write_headers(self, controller):
        headers = controller.data.parent.header[1:]
        if not headers:
            self.SetColLabelSize(0)
            return
        self.SetColLabelSize(wx.grid.GRID_AUTOSIZE)  # DEBUG
        for col, header in enumerate(headers):
            self.SetColLabelValue(col, header)
        for empty_col in range(col + 1, self.NumberCols + 1):
            self.SetColLabelValue(empty_col, '')

    def _colorize_grid(self):
        selection_content = \
            self._get_single_selection_content_or_none_on_first_call()
        if selection_content is None:
            self.highlight(None)
        else:
            self._parent.highlight(selection_content, expand=False)

    def highlight(self, text, expand=True):
        # Below CallAfter was causing C++ assertions(objects not found)
        # When calling Preferences Grid Colors change
        wx.CallLater(100, self._colorizer.colorize, text)

    def autosize(self):
        wx.CallAfter(self.AutoSizeColumns, False)
        wx.CallAfter(self.AutoSizeRows, False)

    def _get_single_selection_content_or_none_on_first_call(self):
        if self._cell_selected:
            return self.get_single_selection_content()

    def _format_comments(self, data):
        # TODO: This should be moved to robot.model
        in_comment = False
        ret = []
        for cell in data:
            if cell.strip().startswith('#'):
                in_comment = True
            if in_comment:
                cell = cell.replace(' |', '')
            ret.append(cell)
        return ret

    def cell_value_edited(self, row, col, value):
        self._execute(ChangeCellValue(row, col, value))
        wx.CallAfter(self.AutoSizeColumn, col, False)
        wx.CallAfter(self.AutoSizeRow, row, False)

    def get_selected_datafile_controller(self):
        return self._controller.datafile_controller

    # DEBUG @requires_focus
    def OnCopy(self, event=None):
        # print("DEBUG: OnCopy called event %s\n" % str(event))
        self.copy()

    # DEBUG @requires_focus
    def OnCut(self, event=None):
        self._clipboard_handler.cut()
        self.OnDelete(event)

    def OnDelete(self, event=None):
        self._execute(
            ClearArea(self.selection.topleft, self.selection.bottomright))
        self._resize_grid()

    # DEBUG    @requires_focus
    def OnPaste(self, event=None):
        if self.IsCellEditControlShown():
            self.paste()
        else:
            self._execute_clipboard_command(PasteArea)
        self._resize_grid()

    def _execute_clipboard_command(self, command_class):
        _iscelleditcontrolshown = self.IsCellEditControlShown()
        if not _iscelleditcontrolshown:
            data = self._clipboard_handler.clipboard_content()
            if data:
                data = [[data]] if isinstance(data, str) else data
                self._execute(command_class(self.selection.topleft, data))

    # DEBUG @requires_focus
    def OnInsert(self, event=None):
        self._execute_clipboard_command(InsertArea)
        self._resize_grid()

    def OnDeleteRows(self, event):
        self._execute(DeleteRows(self.selection.rows()))
        self.ClearSelection()
        self._resize_grid()
        self._skip_except_on_mac(event)

    # DEBUG @requires_focus
    def OnUndo(self, event=None):
        _iscelleditcontrolshown = self.IsCellEditControlShown()
        if not _iscelleditcontrolshown:
            self._execute(Undo())
        else:
            self.GetCellEditor(*self.selection.cell).Reset()
        self._resize_grid()

    # DEBUG @requires_focus
    def OnRedo(self, event=None):
        self._execute(Redo())
        self._resize_grid()

    def close(self):
        self._colorizer.close()
        self.save()
        PUBLISHER.unsubscribe(self._before_saving, RideBeforeSaving)
        PUBLISHER.unsubscribe(self._data_changed, RideItemStepsChanged)
        if self._namespace_updated:
            # Prevent re-entry to unregister method
            self._controller.datafile_controller.unregister_namespace_updates(
                self._namespace_updated)
        self._namespace_updated = None

    def save(self):
        self._tooltips.hide()
        _iscelleditcontrolshown = self.IsCellEditControlShown()
        if _iscelleditcontrolshown:
            cell_editor = self.GetCellEditor(*self.selection.cell)
            cell_editor.EndEdit(self.selection.topleft.row,
                                self.selection.topleft.col, self)

    def show_content_assist(self):
        _iscelleditcontrolshown = self.IsCellEditControlShown()
        if _iscelleditcontrolshown:
            self.GetCellEditor(*self.selection.cell).show_content_assist(
                self.selection.cell)

    def refresh_datafile(self, item, event):
        self._tree.refresh_datafile(item, event)

    def _calculate_position(self):
        x, y = wx.GetMousePosition()
        return x, y + 20

    def OnEditor(self, event):
        self._tooltips.hide()
        row_height = self.GetRowSize(self.selection.topleft.row)
        self.GetCellEditor(*self.selection.cell).SetHeight(row_height)
        event.Skip()

    def _move_cursor_down(self, event):
        self.DisableCellEditControl()
        self.MoveCursorDown(event.ShiftDown())

    def OnKeyDown(self, event):
        keycode = event.GetUnicodeKey()
        specialkcode = event.GetKeyCode()
        if event.ControlDown():
            if event.ShiftDown():
                if keycode == ord('I'):
                    self.OnInsertCells()
                elif keycode == ord('J'):
                    self.OnJsonEditor(event)
                elif keycode == ord('D'):
                    self.OnDeleteCells()
            else:
                if keycode == wx.WXK_SPACE:
                    self._open_cell_editor_with_content_assist()
                elif keycode == ord('C'):
                    self.OnCopy(event)
                elif keycode == ord('X'):
                    self.OnCut(event)
                elif keycode == ord('V'):
                    self.OnPaste(event)
                elif keycode == ord('Z'):
                    self.OnUndo(event)
                elif keycode == ord('A'):
                    self.OnSelectAll(event)
                elif keycode == ord('B'):
                    self._navigate_to_matching_user_keyword(
                        self.GetGridCursorRow(), self.GetGridCursorCol())
                elif keycode == ord('F'):
                    if not self.has_focus():
                        self.SetFocus()  # Avoiding Search field on Text Edit
                elif keycode in (ord('1'), ord('2'), ord('5')):
                    self._open_cell_editor_and_execute_variable_creator(
                        list_variable=(keycode == ord('2')),
                        dict_variable=(keycode == ord('5')))
                else:
                    self.show_cell_information()
        elif event.AltDown():
            if keycode == wx.WXK_SPACE:
                self._open_cell_editor_with_content_assist()  # Mac CMD
            elif specialkcode in [wx.WXK_DOWN, wx.WXK_UP]:
                self._skip_except_on_mac(event)
                self._move_rows(specialkcode)
            elif specialkcode == wx.WXK_RETURN:
                self._move_cursor_down(event)
            else:
                event.Skip()
        else:
            if specialkcode == wx.WXK_WINDOWS_MENU:
                self.OnCellRightClick(event)
            elif specialkcode in [wx.WXK_RETURN, wx.WXK_BACK]:
                self.save()
                self._move_grid_cursor(event, specialkcode)
            elif specialkcode == wx.WXK_F2:
                self._open_cell_editor()
            else:
                event.Skip()
        if specialkcode != wx.WXK_RETURN:
            event.Skip()

    def OnChar(self, event):
        keychar = event.GetUnicodeKey()
        if keychar < ord(' '):
            return
        if keychar in [
                ord('['),
                ord('{'),
                ord('('),
                ord("'"),
                ord('\"'),
                ord('`')
        ]:
            self._open_cell_editor().execute_enclose_text(chr(keychar))
        else:
            event.Skip()

    def OnGoToDefinition(self, event):
        self._navigate_to_matching_user_keyword(self.GetGridCursorRow(),
                                                self.GetGridCursorCol())

    def show_cell_information(self):
        cell = self.cell_under_cursor
        value = self._cell_value(cell)
        if value:
            self._show_user_keyword_link(cell, value)
            self._show_keyword_details(cell, value)

    def _cell_value(self, cell):
        if cell == self._no_cell:
            return None
        return self.GetCellValue(cell.Row, cell.Col)

    def _show_user_keyword_link(self, cell, value):
        if cell != self._marked_cell and self._plugin.get_user_keyword(value):
            self._toggle_underlined(cell)
            self._marked_cell = cell

    def _show_keyword_details(self, cell, value):
        details = self._plugin.get_keyword_details(value)
        if not details:
            info = self._controller.get_cell_info(cell.Row, cell.Col)
            if info.cell_type == CellType.KEYWORD and info.content_type == \
                    ContentType.STRING:
                details = """\
        <b>Keyword was not detected by RIDE</b>
        <br>Possible corrections:<br>
        <ul>
            <li>Import library or resource file containing the keyword.</li>
            <li>For library import errors: Consider importing library spec XML
            (Tools / Import Library Spec XML or by adding the XML file with the
            correct name to PYTHONPATH) to enable keyword completion
            for example for Java libraries.
            Library spec XML can be created using libdoc tool from Robot Frame\
work.</li>
        </ul>"""
        if details:
            self._tooltips.show_info_at(details, value,
                                        self._cell_to_screen_coordinates(cell))

    def _cell_to_screen_coordinates(self, cell):
        point = self.CellToRect(cell.Row, cell.Col).GetTopRight()
        point.x += self.GetRowLabelSize() + 5
        return self.ClientToScreen(self.CalcScrolledPosition(point))

    def _move_rows(self, keycode):
        if keycode == wx.WXK_UP:
            self.OnMoveRowsUp()
        else:
            self.OnMoveRowsDown()

    def _move_grid_cursor(self, event, keycode):
        self.DisableCellEditControl()
        if keycode == wx.WXK_RETURN:
            self.MoveCursorRight(event.ShiftDown())
        else:
            self.MoveCursorLeft(event.ShiftDown())

    def OnKeyUp(self, event):
        event.Skip()  # DEBUG seen this skip as soon as possible
        self._tooltips.hide()
        self._hide_link_if_necessary()
        #  event.Skip()

    def _open_cell_editor(self):
        if not self.IsCellEditControlEnabled():
            self.EnableCellEditControl()
        row = self.GetGridCursorRow()
        celleditor = self.GetCellEditor(self.GetGridCursorCol(), row)
        celleditor.Show(True)
        return celleditor

    def _open_cell_editor_with_content_assist(self):
        wx.CallAfter(self._open_cell_editor().show_content_assist)

    def _open_cell_editor_and_execute_variable_creator(self,
                                                       list_variable=False,
                                                       dict_variable=False):
        wx.CallAfter(self._open_cell_editor().execute_variable_creator,
                     list_variable, dict_variable)

    def OnMakeVariable(self, event):
        self._open_cell_editor_and_execute_variable_creator(
            list_variable=False)

    def OnMakeListVariable(self, event):
        self._open_cell_editor_and_execute_variable_creator(list_variable=True)

    def OnMakeDictVariable(self, event):
        self._open_cell_editor_and_execute_variable_creator(dict_variable=True)

    def OnCellRightClick(self, event):
        self._tooltips.hide()
        self._popup_menu_shown = True
        GridEditor.OnCellRightClick(self, event)
        self._popup_menu_shown = False

    def OnSelectAll(self, event):
        self.SelectAll()

    def OnCellColSizeChanged(self, event):
        wx.CallAfter(self.AutoSizeRows, False)
        event.Skip()

    def OnCellLeftClick(self, event):
        self._tooltips.hide()
        if event.ControlDown():
            if self._navigate_to_matching_user_keyword(event.Row, event.Col):
                return
        if not self._has_been_clicked:
            self.SetGridCursor(event.Row, event.Col)
            self._has_been_clicked = True
        else:
            event.Skip()

    def OnCellLeftDClick(self, event):
        self._tooltips.hide()
        if not self._navigate_to_matching_user_keyword(event.Row, event.Col):
            event.Skip()

    def _navigate_to_matching_user_keyword(self, row, col):
        value = self.GetCellValue(row, col)
        uk = self._plugin.get_user_keyword(value)
        if uk:
            self._toggle_underlined((grid.GridCellCoords(row, col)))
            self._marked_cell = None
            wx.CallAfter(self._tree.select_user_keyword_node, uk)
            return True
        return False

    def _is_active_window(self):
        return self.IsShownOnScreen() and self.FindFocus()

    def _hide_link_if_necessary(self):
        if not self._marked_cell:
            return
        self._toggle_underlined(self._marked_cell)
        self._marked_cell = None

    def OnCreateKeyword(self, event):
        cells = self._data_cells_from_current_row()
        if not cells:
            return
        try:
            self._execute(AddKeywordFromCells(cells))
        except ValueError as err:
            wx.MessageBox(str(err))

    def _data_cells_from_current_row(self):
        currow, curcol = self.selection.cell
        rowdata = self._row_data(currow)[curcol:]
        return self._strip_trailing_empty_cells(self._remove_comments(rowdata))

    def _remove_comments(self, data):
        for index, cell in enumerate(data):
            if cell.strip().startswith('#'):
                return data[:index]
        return data

    def OnExtractKeyword(self, event):
        dlg = UserKeywordNameDialog(self._controller)
        if dlg.ShowModal() == wx.ID_OK:
            name, args = dlg.get_value()
            rows = self.selection.topleft.row, self.selection.bottomright.row
            self._execute(ExtractKeyword(name, args, rows))

    def OnExtractVariable(self, event):
        cells = self.selection.cells()
        if len(cells) == 1:
            self._extract_scalar(cells[0])
        elif min(row for row, _ in cells) == max(row for row, _ in cells):
            self._extract_list(cells)
        self._resize_grid()

    def OnFindWhereUsed(self, event):
        is_variable, searchstring = self._get_is_variable_and_searchstring()
        if searchstring:
            self._execute_find_where_used(is_variable, searchstring)

    def _get_is_variable_and_searchstring(self):
        cellvalue = self.GetCellValue(*self.selection.cells()[0])
        if self._cell_value_contains_multiple_search_items(cellvalue):
            choice_dialog = ChooseUsageSearchStringDialog(cellvalue)
            choice_dialog.ShowModal()
            is_var, value = choice_dialog.GetStringSelection()
            choice_dialog.Destroy()
            return is_var, value
        else:
            return variablematcher.is_variable(cellvalue), cellvalue

    def _execute_find_where_used(self, is_variable, searchstring):
        usages_dialog_class = VariableUsages if is_variable else Usages
        usages_dialog_class(self._controller, self._tree.highlight,
                            searchstring).show()

    def _cell_value_contains_multiple_search_items(self, value):
        variables = variablematcher.find_variable_basenames(value)
        return variables and variables[0] != value

    def _extract_scalar(self, cell):
        var = robotapi.Variable(self._controller.datafile.variable_table, '',
                                self.GetCellValue(*cell), '')
        dlg = ScalarVariableDialog(
            self._controller.datafile_controller.variables, var)
        if dlg.ShowModal() == wx.ID_OK:
            name, value = dlg.get_value()
            comment = dlg.get_comment()
            self._execute(ExtractScalar(name, value, comment, cell))

    def _extract_list(self, cells):
        var = robotapi.Variable(self._controller.datafile.variable_table, '',
                                [self.GetCellValue(*cell) for cell in cells],
                                '')
        dlg = ListVariableDialog(
            self._controller.datafile_controller.variables, var, self._plugin)
        if dlg.ShowModal() == wx.ID_OK:
            name, value = dlg.get_value()
            comment = dlg.get_comment()
            self._execute(ExtractList(name, value, comment, cells))

    def OnRenameKeyword(self, event):
        old_name = self._current_cell_value()
        if not old_name.strip() or variablematcher.is_variable(old_name):
            return
        new_name = wx.GetTextFromUser('New name',
                                      'Rename Keyword',
                                      default_value=old_name)
        if new_name:
            self._execute(
                RenameKeywordOccurrences(
                    old_name, new_name,
                    RenameProgressObserver(self.GetParent())))

    # Add one new Dialog to edit pretty json String
    def OnJsonEditor(self, event=None):
        if event:
            event.Skip()
        dialog = Dialog()
        dialog.SetTitle('JSON Editor')
        dialog.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
        okBtn = wx.Button(dialog, wx.ID_OK, "Save")
        cnlBtn = wx.Button(dialog, wx.ID_CANCEL, "Cancel")
        richText = wx.TextCtrl(dialog,
                               wx.ID_ANY, "If supported by the native "
                               "control, this is reversed, "
                               "and this is a different "
                               "font.",
                               size=(400, 475),
                               style=wx.HSCROLL | wx.TE_MULTILINE
                               | wx.TE_NOHIDESEL)
        dialog.Sizer.Add(richText, flag=wx.GROW, proportion=1)
        dialog.Sizer.Add(okBtn, flag=wx.ALIGN_RIGHT | wx.ALL)
        dialog.Sizer.Add(cnlBtn, flag=wx.ALL)
        # Get cell value of parent grid
        if self.is_json(self._current_cell_value()):
            jsonStr = json.loads(self._current_cell_value())
            richText.SetValue(json.dumps(jsonStr, indent=4,
                                         ensure_ascii=False))
        else:
            richText.SetValue(self._current_cell_value())
        dialog.SetSize((650, 550))
        # If click Save, then save the value in richText into the original
        # grid cell, and clear all indent.
        if dialog.ShowModal() == wx.ID_OK:
            try:
                strJson = json.loads(richText.GetValue())
                self.cell_value_edited(self.selection.cell[0],
                                       self.selection.cell[1],
                                       json.dumps(strJson, ensure_ascii=False))
            except ValueError or json.JSONDecodeError as e:
                res = wx.MessageDialog(
                    dialog, "Error in JSON: {}\n\n"
                    "Save anyway?".format(e), "Validation Error!",
                    wx.YES_NO).ShowModal()
                if res == wx.ID_YES:
                    self.cell_value_edited(self.selection.cell[0],
                                           self.selection.cell[1],
                                           richText.GetValue())
                else:
                    pass

    # If the jsonStr is json format, then return True
    def is_json(self, jsonStr):
        try:
            json.loads(jsonStr)
        except ValueError:
            return False
        return True
Beispiel #2
0
class Tree(
        with_metaclass(classmaker(), treemixin.DragAndDrop,
                       customtreectrl.CustomTreeCtrl, utils.RideEventHandler)):
    _RESOURCES_NODE_LABEL = 'External Resources'

    def __init__(self, parent, action_registerer, settings=None):
        self._checkboxes_for_tests = False
        self._test_selection_controller = \
            self._create_test_selection_controller()
        self._controller = TreeController(
            self,
            action_registerer,
            settings=settings,
            test_selection=self._test_selection_controller)
        treemixin.DragAndDrop.__init__(self, parent, **_TREE_ARGS)
        self._controller.register_tree_actions()
        self._bind_tree_events()
        self._images = TreeImageList()
        self._animctrl = None
        self._silent_mode = False
        self.SetImageList(self._images)
        self.label_editor = TreeLabelEditListener(self, action_registerer)
        self._controller.bind_keys()
        self._subscribe_to_messages()
        self._popup_creator = PopupCreator()
        self._dragging = False
        self._clear_tree_data()
        self._editor = None
        self._execution_results = None
        self._resources = []
        self.SetBackgroundColour('white')  # TODO get background color from def
        if not hasattr(self, 'OnCancelEdit'):
            self.OnCancelEdit = self._on_cancel_edit

    def _create_test_selection_controller(self):
        tsc = TestSelectionController()
        PUBLISHER.subscribe(tsc.clear_all, RideOpenSuite)
        PUBLISHER.subscribe(tsc.clear_all, RideNewProject)
        return tsc

    def _on_cancel_edit(self, item):
        le = customtreectrl.TreeEvent(customtreectrl.wxEVT_TREE_END_LABEL_EDIT,
                                      self.GetId())
        le._item = item
        le.SetEventObject(self)
        le._label = ""
        le._editCancelled = True
        self.GetEventHandler().ProcessEvent(le)

    def _bind_tree_events(self):
        self.Bind(wx.EVT_LEFT_DCLICK, self.OnDoubleClick)
        self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnSelChanged)
        self.Bind(wx.EVT_TREE_ITEM_EXPANDING, self.OnTreeItemExpanding)
        self.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK, self.OnRightClick)
        self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnItemActivated)
        self.Bind(customtreectrl.EVT_TREE_ITEM_CHECKED, self.OnTreeItemChecked)
        self.Bind(wx.EVT_TREE_ITEM_COLLAPSING, self.OnTreeItemCollapsing)
        self.Bind(wx.EVT_CLOSE, self.OnClose)

    def OnDoubleClick(self, event):
        item, pos = self.HitTest(self.ScreenToClient(wx.GetMousePosition()))
        if item:
            handler = self._controller.get_handler(item)
            handler.double_clicked()
        event.Skip()

    def set_editor(self, editor):
        self._editor = editor

    def StartDragging(self):
        self._dragging = True
        treemixin.DragAndDrop.StartDragging(self)

    def OnEndDrag(self, event):
        self._dragging = False
        treemixin.DragAndDrop.OnEndDrag(self, event)

    def register_context_menu_hook(self, callable):
        self._popup_creator.add_hook(callable)

    def unregister_context_menu_hook(self, callable):
        self._popup_creator.remove_hook(callable)

    def _subscribe_to_messages(self):
        subscriptions = [
            (self._item_changed, RideItem),
            (self._resource_added, RideOpenResource),
            (self._select_resource, RideSelectResource),
            (self._suite_added, RideSuiteAdded),
            (self._keyword_added, RideUserKeywordAdded),
            (self._test_added, RideTestCaseAdded),
            (self._variable_added, RideVariableAdded),
            (self._leaf_item_removed, RideUserKeywordRemoved),
            (self._leaf_item_removed, RideTestCaseRemoved),
            (self._leaf_item_removed, RideVariableRemoved),
            (self._datafile_removed, RideDataFileRemoved),
            (self._datafile_set, RideDataFileSet),
            (self._data_dirty, RideDataChangedToDirty),
            (self._data_undirty, RideDataDirtyCleared),
            (self._variable_moved_up, RideVariableMovedUp),
            (self._variable_moved_down, RideVariableMovedDown),
            (self._variable_updated, RideVariableUpdated),
            (self._filename_changed, RideFileNameChanged),
            (self._testing_started, RideTestExecutionStarted),
            (self._test_result, RideTestRunning),
            (self._test_result, RideTestPaused),
            (self._test_result, RideTestPassed),
            (self._test_result, RideTestFailed),
            (self._handle_import_setting_message, RideImportSetting),
            (self._mark_excludes, RideExcludesChanged),
            (self._mark_excludes, RideIncludesChanged),
        ]
        for listener, topic in subscriptions:
            PUBLISHER.subscribe(listener, topic)

    def _mark_excludes(self, message):
        tree = self._controller.find_node_by_controller(message.old_controller)
        self._render_datafile(self.GetItemParent(tree), message.new_controller)
        self._remove_datafile_node(tree)

    def _set_item_excluded(self, node):
        self.SetItemTextColour(node, wx.TheColourDatabase.Find("GRAY"))
        self.SetItemItalic(node, True)
        self.SetItemText(node, "%s (excluded)" % self.GetItemText(node))

    def _handle_import_setting_message(self, message):
        if message.is_resource():
            self._set_resource_color(
                message.import_controller.get_imported_controller())
            self._set_resource_color(
                message.import_controller.get_previous_imported_controller())

    def _set_resource_color(self, resource_controller):
        if not resource_controller:
            return
        node = self._controller.find_node_by_controller(resource_controller)
        if node:
            self.SetItemTextColour(
                node, self._get_resource_text_color(resource_controller))

    def _get_resource_text_color(self, resource_controller):
        if resource_controller.is_used():
            return self.GetDefaultAttributes().colFg
        else:
            return wx.LIGHT_GREY

    def _testing_started(self, message):
        self._for_all_drawn_tests(
            self._root, lambda t: self.SetItemImage(t, ROBOT_IMAGE_INDEX))
        self._execution_results = message.results
        self._images.set_execution_results(message.results)

    def _test_result(self, message):
        wx.CallAfter(self._set_icon_from_execution_results, message.item)

    def _set_icon_from_execution_results(self, controller):
        node = self._controller.find_node_by_controller(controller)
        if not node:
            return
        img_index = self._get_icon_index_for(controller)
        # Always set the static icon
        self.SetItemImage(node, img_index)
        if self._animctrl:
            self._animctrl.Stop()
            self._animctrl.Animation.Destroy()
            self._animctrl.Destroy()
            self._animctrl = None
            self.DeleteItemWindow(node)
        if img_index in (RUNNING_IMAGE_INDEX, PAUSED_IMAGE_INDEX):
            from wx.adv import Animation, AnimationCtrl
            import os
            _BASE = os.path.join(os.path.dirname(__file__), '..', 'widgets')
            if img_index == RUNNING_IMAGE_INDEX:
                img = os.path.join(_BASE, 'robot-running.gif')
            else:
                img = os.path.join(_BASE, 'robot-pause.gif')
            ani = Animation(img)
            obj = self
            rect = (node.GetX() + 20, node.GetY())  # Overlaps robot icon
            self._animctrl = AnimationCtrl(obj, -1, ani, rect)
            self._animctrl.SetBackgroundColour(obj.GetBackgroundColour())
            self.SetItemWindow(node, self._animctrl, False)
            self._animctrl.Play()
        # Make visible the running or paused test
        self.EnsureVisible(node)

    def _get_icon_index_for(self, controller):
        if not self._execution_results:
            return ROBOT_IMAGE_INDEX
        if self._execution_results.is_paused(controller):
            return PAUSED_IMAGE_INDEX
        if self._execution_results.is_running(controller):
            return RUNNING_IMAGE_INDEX
        if self._execution_results.has_passed(controller):
            return PASSED_IMAGE_INDEX
        if self._execution_results.has_failed(controller):
            return FAILED_IMAGE_INDEX
        return ROBOT_IMAGE_INDEX

    def populate(self, model):
        self._clear_tree_data()
        self._populate_model(model)
        self._refresh_view()
        self.SetFocus()  # Needed for keyboard shortcuts

    def _clear_tree_data(self):
        self.DeleteAllItems()
        self._root = self.AddRoot('')
        self._resource_root = self._create_resource_root()
        self._datafile_nodes = []
        self._resources = []

    def _create_resource_root(self):
        return self._create_node(self._root, self._RESOURCES_NODE_LABEL,
                                 self._images.directory)

    def _populate_model(self, model):
        handler = ResourceRootHandler(model, self, self._resource_root,
                                      self._controller.settings)
        self.SetPyData(self._resource_root, handler)
        if model.data:
            self._render_datafile(self._root, model.data, 0)
        for res in model.external_resources:
            if not res.parent:
                self._render_datafile(self._resource_root, res)

    def _resource_added(self, message):
        ctrl = message.datafile
        if self._controller.find_node_by_controller(ctrl):
            return

        parent = None
        if ctrl.parent:
            parent = self._get_dir_node(ctrl.parent)
        else:
            parent = self._resource_root
        self._render_datafile(parent, ctrl)

    def _get_dir_node(self, ctrl):
        if ctrl is None:
            return self._root
        dir_node = self._get_datafile_node(ctrl.data)
        if dir_node is None:
            parent = self._get_dir_node(ctrl.parent)
            self._render_datafile(parent, ctrl)
            dir_node = self._get_datafile_node(ctrl.data)
        return dir_node

    def _select_resource(self, message):
        self.select_controller_node(message.item)

    def select_controller_node(self, controller):
        self.SelectItem(self._controller.find_node_by_controller(controller))

    def _suite_added(self, message):
        self.add_datafile(message.parent, message.suite)

    def _refresh_view(self):
        self.Refresh()
        if self._resource_root:
            self.Expand(self._resource_root)
        if self._datafile_nodes:
            self.SelectItem(self._datafile_nodes[0])
            self._expand_and_render_children(self._datafile_nodes[0])

    def _render_datafile(self, parent_node, controller, index=None):
        node = self._create_node_with_handler(parent_node, controller, index)
        if not node:
            return None
        if controller.dirty:
            self._controller.mark_node_dirty(node)
        self._datafile_nodes.append(node)
        self.SetItemHasChildren(node, True)

        for child in controller.children:
            self._render_datafile(node, child)
        return node

    def _normalize(self, path):
        return os.path.normcase(os.path.normpath(os.path.abspath(path)))

    def _create_node_with_handler(self, parent_node, controller, index=None):
        if IS_WINDOWS and isinstance(controller, ResourceFileController):
            resourcefile = self._normalize(controller.filename)
            pname = parent_node.GetText()
            self._resources.append((pname, resourcefile))
            if IS_WINDOWS:
                count = 0
                for (p, r) in self._resources:
                    if (p, r) == (pname, resourcefile):
                        count += 1
                if count > 3:
                    return None
        handler_class = action_handler_class(controller)
        with_checkbox = (handler_class == TestCaseHandler
                         and self._checkboxes_for_tests)
        node = self._create_node(parent_node,
                                 controller.display_name,
                                 self._images[controller],
                                 index,
                                 with_checkbox=with_checkbox)
        if isinstance(controller,
                      ResourceFileController) and not controller.is_used():
            self.SetItemTextColour(node, TREETEXTCOLOUR)  # wxPython3 hack
        action_handler = handler_class(controller, self, node,
                                       self._controller.settings)
        self.SetPyData(node, action_handler)

        # if we have a TestCase node we have to make sure that
        # we retain the checked state
        if (handler_class == TestCaseHandler and self._checkboxes_for_tests) \
                and self._test_selection_controller.is_test_selected(controller):
            self.CheckItem(node, True)
        if controller.is_excluded():
            self._set_item_excluded(node)
        return node

    def set_checkboxes_for_tests(self):
        self._checkboxes_for_tests = True

    def _expand_and_render_children(self, node):
        assert node is not None
        self._render_children(node)
        self.Expand(node)

    def _render_children(self, node):
        handler = self._controller.get_handler(node)
        if not handler or not handler.can_be_rendered:
            return
        self._create_child_nodes(node, handler,
                                 lambda item: item.is_test_suite)
        handler.set_rendered()

    def _create_child_nodes(self, node, handler, predicate):
        for childitem in self._children_of(handler):
            index = self._get_insertion_index(node, predicate)
            self._create_node_with_handler(node, childitem, index)

    def _children_of(self, handler):
        return [v for v in handler.variables if v.has_data()] + \
               list(handler.tests) + list(handler.keywords)

    def _create_node(self,
                     parent_node,
                     label,
                     img,
                     index=None,
                     with_checkbox=False):
        node = self._wx_node(parent_node, index, label, with_checkbox)
        self.SetItemImage(node, img.normal, wx.TreeItemIcon_Normal)
        self.SetItemImage(node, img.expanded, wx.TreeItemIcon_Expanded)
        return node

    def _wx_node(self, parent_node, index, label, with_checkbox):
        ct_type = 1 if with_checkbox else 0
        if index is not None:
            # blame wxPython for this ugliness
            if isinstance(index, int):
                return self.InsertItemByIndex(parent_node,
                                              index,
                                              label,
                                              ct_type=ct_type)
            else:
                return self.InsertItem(parent_node,
                                       index,
                                       label,
                                       ct_type=ct_type)
        return self.AppendItem(parent_node, label, ct_type=ct_type)

    def add_datafile(self, parent, suite):
        snode = self._render_datafile(self._get_datafile_node(parent.data),
                                      suite)
        self.SelectItem(snode)

    def add_test(self, parent_node, test):
        self._add_dataitem(parent_node, test,
                           lambda item: item.is_user_keyword)

    def add_keyword(self, parent_node, kw):
        self._add_dataitem(parent_node, kw, lambda item: item.is_test_suite)

    def _add_dataitem(self, parent_node, dataitem, predicate):
        node = self._get_or_create_node(parent_node, dataitem, predicate)
        self._select(node)
        self._controller.mark_node_dirty(parent_node)

    def _get_or_create_node(self, parent_node, dataitem, predicate):
        if not self.IsExpanded(parent_node):
            self._expand_and_render_children(parent_node)
            return self._controller.find_node_with_label(
                parent_node, dataitem.display_name)

        index = self._get_insertion_index(parent_node, predicate)
        return self._create_node_with_handler(parent_node, dataitem, index)

    def _select(self, node):
        if node:
            wx.CallAfter(self.SelectItem, node)

    def _get_insertion_index(self, parent_node, predicate):
        if not predicate:
            return None
        item, cookie = self.GetFirstChild(parent_node)
        while item:
            if predicate(self._controller.get_handler(item)):
                index = self.GetPrevSibling(item)
                if not index:
                    index = 0
                return index
            item, cookie = self.GetNextChild(parent_node, cookie)
        return None

    def _keyword_added(self, message):
        self.add_keyword(self._get_datafile_node(self.get_selected_datafile()),
                         message.item)

    def _variable_added(self, message):
        self._get_or_create_node(
            self._get_datafile_node(self.get_selected_datafile()),
            message.item,
            lambda item: not item.is_variable or item.index > message.index)

    def _leaf_item_removed(self, message):
        node = self._controller.find_node_by_controller(message.item)
        parent_node = self._get_datafile_node(message.datafile)
        # DEBUG The below call causes not calling delete_node
        # self._test_selection_controller.select(message.item, False)
        self._controller.mark_node_dirty(parent_node)
        self.delete_node(node)

    def _test_added(self, message):
        self.add_test(self._get_datafile_node(self.get_selected_datafile()),
                      message.item)

    def _datafile_removed(self, message):
        dfnode = self._get_datafile_node(message.datafile.data)
        self._datafile_nodes.remove(dfnode)
        self.DeleteChildren(dfnode)
        self.Delete(dfnode)

    def _datafile_set(self, message):
        wx.CallAfter(self._refresh_datafile_when_file_set, message.item)

    def _filename_changed(self, message):
        df = message.datafile
        node = self._controller.find_node_by_controller(df)
        if not node:
            raise AssertionError('No node found with controller "%s"' % df)
        wx.CallAfter(self.SetItemText, node, df.display_name)

    def add_keyword_controller(self, controller):
        parent = self._get_datafile_node(self.get_selected_datafile())
        self.add_keyword(parent, controller)

    def delete_node(self, node):
        if node is None:
            return
        parent = self.GetItemParent(node)
        self._controller.mark_node_dirty(parent)
        if self.IsSelected(node):
            wx.CallAfter(self.SelectItem, parent)
        wx.CallAfter(self.Delete, node)

    def _data_dirty(self, message):
        self._controller.mark_controller_dirty(message.datafile)

    def _data_undirty(self, message):
        self.unset_dirty()

    def unset_dirty(self):
        for node in self._datafile_nodes:
            text = self.GetItemText(node)
            handler = self._controller.get_handler(node)
            if text.startswith('*') and not handler.controller.dirty:
                self.SetItemText(node, text[1:])

    def select_node_by_data(self, controller):
        """Find and select the tree item associated with the given controller.

        Controller can be any of the controllers that are represented in the
        tree."""
        parent_node = self._get_datafile_node(controller.datafile)
        if not parent_node:
            return None
        if not self.IsExpanded(parent_node):
            self._expand_and_render_children(parent_node)
        node = self._controller.find_node_by_controller(controller)
        if node != self.GetSelection():
            self.SelectItem(node)
        return node

    def select_user_keyword_node(self, uk):
        parent_node = self._get_datafile_node(uk.parent.parent)
        if not parent_node:
            return
        if not self.IsExpanded(parent_node):
            self._expand_and_render_children(parent_node)
        node = self._controller.find_node_with_label(parent_node,
                                                     utils.normalize(uk.name))
        if node != self.GetSelection():
            self.SelectItem(node)

    def _get_datafile_node(self, datafile):
        for node in self._datafile_nodes:
            if self._controller.get_handler(node).item == datafile:
                return node
        return None

    def get_selected_datafile(self):
        """Returns currently selected data file.

        If a test or user keyword node is selected, returns parent of that
        item."""
        datafile = self._get_selected_datafile_node()
        if not datafile:
            return None
        return self._controller.get_handler(datafile).item

    def get_selected_datafile_controller(self):
        """Returns controller associated with currently active data file.

        If a test or user keyword node is selected, returns parent of that
        item."""
        dfnode = self._get_selected_datafile_node()

        if dfnode:
            return self._controller.get_handler(dfnode).controller
        else:
            return None

    def _get_selected_datafile_node(self):
        node = self.GetSelection()
        if not node or node in (self._resource_root, self._root):
            return None
        while node not in self._datafile_nodes:
            node = self.GetItemParent(node)
        return node

    def get_selected_item(self):
        """Returns model object associated with currently selected tree node.
        """
        selection = self.GetSelection()
        if not selection:
            return None
        handler = self._controller.get_handler(selection)
        return handler and handler.controller or None

    def move_up(self, node):
        prev = self.GetPrevSibling(node)
        if prev.IsOk():
            self._switch_items(prev, node, node)

    def move_down(self, node):
        next = self.GetNextSibling(node)
        if next.IsOk():
            self._switch_items(node, next, node)

    def _switch_items(self, first, second, currently_selected):
        """Changes the order of given items, first is expected to be directly
        above the second"""
        selection = self.GetItemPyData(currently_selected).controller
        controller = self._controller.get_handler(first).controller
        self.Delete(first)
        self._create_node_with_handler(self.GetItemParent(second), controller,
                                       second)
        self.select_node_by_data(selection)

    def _refresh_datafile_when_file_set(self, controller):
        # Prevent tab selections based on tree item selected events
        self._start_silent_mode()
        current = self.get_selected_datafile_controller()
        if not current:  # If tree is not yet in use - do not expand anything.
            self._end_silent_mode()
            return

        item = self.GetSelection()
        current_txt = self.GetItemText(item) if item.IsOk() else ''
        # after refresh current and current_txt might have been changed
        node = self._refresh_datafile(controller)
        if node is None:
            # TODO: Find out why this sometimes happens
            return
        self._expand_and_render_children(node)
        if current == controller:
            select_item = self._controller.find_node_with_label(
                node, current_txt)
            if select_item is None:
                select_item = node
            wx.CallAfter(self.SelectItem, select_item)
            wx.CallAfter(self._end_silent_mode)
        else:
            self._end_silent_mode()

    def _uncheck_tests(self, controller):
        self._test_selection_controller.unselect_all(controller.tests)

    def _start_silent_mode(self):
        self._silent_mode = True

    def _end_silent_mode(self):
        self._silent_mode = False

    def refresh_datafile(self, controller, event):
        to_be_selected = self._get_pending_selection(event)
        new_node = self._refresh_datafile(controller)
        self._handle_pending_selection(to_be_selected, new_node)

    def _refresh_datafile(self, controller):
        orig_node = self._get_data_controller_node(controller)
        if orig_node is not None:
            insertion_index = self._get_datafile_index(orig_node)
            parent = self.GetItemParent(orig_node)
            self._remove_datafile_node(orig_node)
            return self._render_datafile(parent, controller, insertion_index)

    def _get_pending_selection(self, event):
        if hasattr(event, 'Item'):
            item = event.GetItem()
            event.Veto()
        elif hasattr(event, 'Position'):
            item, flags = self.HitTest(event.Position)
            if not self._click_on_item(item, flags):
                return
        else:
            return
        return self.GetItemText(item)

    def _get_data_controller_node(self, controller):
        for node in self._datafile_nodes:
            if self.GetItemPyData(node).controller == controller:
                return node
        return None

    def _click_on_item(self, item, flags):
        return item is not None and item.IsOk() and \
            flags & wx.TREE_HITTEST_ONITEM

    def _get_datafile_index(self, node):
        insertion_index = self.GetPrevSibling(node)
        if not insertion_index:
            insertion_index = 0
        return insertion_index

    def _remove_datafile_node(self, node):
        for child in self.GetItemChildren(node):
            if child in self._datafile_nodes:
                self._remove_datafile_node(child)
        self._datafile_nodes.remove(node)
        self.Delete(node)

    def _handle_pending_selection(self, to_be_selected, parent_node):
        if to_be_selected:
            self._expand_and_render_children(parent_node)
            select_item = self._controller.find_node_with_label(
                parent_node, to_be_selected)
            wx.CallAfter(self.SelectItem, select_item)

    def OnSelChanged(self, event):
        node = event.GetItem()
        if not node.IsOk() or self._dragging:
            event.Skip()
            return
        self._controller.add_to_history(node)
        handler = self._controller.get_handler(node)
        if handler and handler.item:
            RideTreeSelection(node=node,
                              item=handler.controller,
                              silent=self._silent_mode).publish()
        self.SetFocus()

    def OnTreeItemExpanding(self, event):
        node = event.GetItem()
        if node.IsOk():
            self._render_children(node)

    # This exists because CustomTreeItem does not remove animations
    def OnTreeItemCollapsing(self, event):
        item = event.GetItem()
        self._hide_item(item)
        event.Skip()

    def _hide_item(self, item):
        for item in item.GetChildren():
            itemwindow = item.GetWindow()
            if itemwindow:
                itemwindow.Hide()
            if self.ItemHasChildren(item):
                self._hide_item(item)

    def SelectAllTests(self, item):
        self._for_all_tests(item, lambda t: self.CheckItem(t))

    def SelectTests(self, tests):
        def foo(t):
            if self.GetPyData(t).controller in tests:
                self.CheckItem(t)

        self._for_all_tests(self._root, foo)

    def ExpandAllSubNodes(self, item):
        self._expand_or_collapse_nodes(item, self.Expand)

    def CollapseAllSubNodes(self, item):
        self._expand_or_collapse_nodes(item, self.Collapse)

    def _expand_or_collapse_nodes(self, item, callback):
        if not self.HasAGWFlag(customtreectrl.TR_HIDE_ROOT) or \
                item != self.GetRootItem():
            callback(item)
            for child in item.GetChildren():
                self._expand_or_collapse_nodes(child, callback)

    def _for_all_tests(self, item, func):
        item_was_expanded = self.IsExpanded(item)
        if not self.HasAGWFlag(customtreectrl.TR_HIDE_ROOT) or \
                item != self.GetRootItem():
            if isinstance(item.GetData(), ResourceRootHandler
                          or ResourceFileHandler):
                return

            is_item_expanded = self.IsExpanded(item)
            if not is_item_expanded:
                self.Expand(item)
            if self._is_test_node(item):
                func(item)
            if not self.IsExpanded(item):
                return

        for child in item.GetChildren():
            self._for_all_tests(child, func)

        if not item_was_expanded:
            self.Collapse(item)

    def _for_all_drawn_tests(self, item, func):
        if self._is_test_node(item):
            func(item)
        for child in item.GetChildren():
            self._for_all_drawn_tests(child, func)

    def _is_test_node(self, node):
        return node.GetType() == 1

    def DeselectAllTests(self, item):
        self._for_all_tests(item, lambda t: self.CheckItem(t, checked=False))

    def DeselectTests(self, tests):
        def foo(t):
            if self.GetPyData(t).controller in tests:
                self.CheckItem(t, checked=False)

        self._for_all_tests(self._root, foo)

    def SelectFailedTests(self, item):
        def func(t):
            # FIXME: This information should be in domain model!
            is_checked = self.GetItemImage(t) == FAILED_IMAGE_INDEX
            self.CheckItem(t, checked=is_checked)

        self._for_all_tests(item, func)

    def SelectPassedTests(self, item):
        def func(t):
            is_checked = self.GetItemImage(t) == PASSED_IMAGE_INDEX
            self.CheckItem(t, checked=is_checked)

        self._for_all_tests(item, func)

    def OnClose(self, event):
        print("DEBUG: Tree OnClose hidding")
        self.Hide()

    def OnTreeItemChecked(self, event):
        node = event.GetItem()
        handler = self._controller.get_handler(node=node)
        self._test_selection_controller.select(handler.controller,
                                               node.IsChecked())

    def OnItemActivated(self, event):
        node = event.GetItem()
        if self.IsExpanded(node):
            self.Collapse(node)
        elif self.ItemHasChildren(node):
            self._expand_and_render_children(node)

    def OnLeftArrow(self, event):
        node = self.GetSelection()
        if self.IsExpanded(node):
            self.Collapse(node)
        else:
            event.Skip()

    def OnRightClick(self, event):
        handler = None
        if hasattr(event, 'GetItem'):
            handler = self._controller.get_handler(event.GetItem())

        if handler:
            if not self.IsExpanded(handler.node):
                self.Expand(handler.node)
            handler.show_popup()

    def OnNewTestCase(self, event):
        handler = self._controller.get_handler()
        if handler:
            handler.OnNewTestCase(event)

    def OnDrop(self, target, dragged):
        dragged = self._controller.get_handler(dragged)
        target = self._controller.get_handler(target)
        if target and target.accepts_drag(dragged):
            dragged.controller.execute(MoveTo(target.controller))
        self.Refresh()  # DEBUG Always refresh

    def IsValidDragItem(self, item):
        return self._controller.get_handler(item).is_draggable

    def OnMoveUp(self, event):
        handler = self._controller.get_handler()
        if handler.is_draggable:
            handler.OnMoveUp(event)

    def OnMoveDown(self, event):
        handler = self._controller.get_handler()
        if handler.is_draggable:
            handler.OnMoveDown(event)

    def _item_changed(self, data):
        controller = data.item
        node = self._controller.find_node_by_controller(controller)
        if node:
            self.SetItemText(node, data.item.name)
            self._test_selection_controller.send_selection_changed_message()
        if controller.dirty:
            self._controller.mark_node_dirty(
                self._get_datafile_node(controller.datafile))

    def _variable_moved_up(self, data):
        if self._should_update_variable_positions(data):
            self._do_action_if_datafile_node_is_expanded(self.move_up, data)

    def _variable_moved_down(self, data):
        if self._should_update_variable_positions(data):
            self._do_action_if_datafile_node_is_expanded(self.move_down, data)

    def _should_update_variable_positions(self, message):
        return message.item != message.other and message.item.has_data() and \
            message.other.has_data()

    def _do_action_if_datafile_node_is_expanded(self, action, data):
        if self.IsExpanded(self._get_datafile_node(data.item.datafile)):
            node = self._controller.find_node_by_controller(data.item)
            action(node)

    def _variable_updated(self, data):
        self._item_changed(data)

    def highlight(self, data, text):
        self.select_node_by_data(data)
        self._editor.highlight(text)

    def node_is_resource_file(self, node):
        return self._controller.get_handler(node).__class__ == \
            ResourceFileHandler
Beispiel #3
0
class RideFrame(with_metaclass(classmaker(), wx.Frame, RideEventHandler)):
    def __init__(self, application, controller):
        size = application.settings.get('mainframe size', (1100, 700))
        wx.Frame.__init__(self,
                          parent=None,
                          id=wx.ID_ANY,
                          title='RIDE',
                          pos=application.settings.get('mainframe position',
                                                       (50, 30)),
                          size=size,
                          style=wx.DEFAULT_FRAME_STYLE | wx.SUNKEN_BORDER)

        # set Left to Right direction (while we don't have localization)
        self.SetLayoutDirection(wx.Layout_LeftToRight)
        # self.SetLayoutDirection(wx.Layout_RightToLeft)

        self._mgr = aui.AuiManager()

        # tell AuiManager to manage this frame
        self._mgr.SetManagedWindow(self)

        # set frame icon
        # self.SetIcon(Icon('widgets/robot.ico')) # Maybe is not needed
        # self.SetMinSize(size)
        self.SetMinSize(wx.Size(400, 300))

        self.ensure_on_screen()
        if application.settings.get('mainframe maximized', False):
            self.Maximize()
        self._application = application
        self._controller = controller
        self.favicon = Icon(
            os.path.join(os.path.dirname(__file__), "..", "widgets",
                         "robot.ico"), wx.BITMAP_TYPE_ICO, 256, 256)
        self.SetIcon(self.favicon)  #TODO use SetIcons for all sizes
        self._init_ui()
        self._plugin_manager = PluginManager(self.notebook)
        self._review_dialog = None
        self._view_all_tags_dialog = None
        self.Bind(wx.EVT_CLOSE, self.OnClose)
        self.Bind(wx.EVT_SIZE, self.OnSize)
        self.Bind(wx.EVT_MOVE, self.OnMove)
        self.Bind(wx.EVT_MAXIMIZE, self.OnMaximize)
        self.Bind(wx.EVT_DIRCTRL_FILEACTIVATED, self.OnOpenFile)
        self.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK, self.OnMenuOpenFile)
        self._subscribe_messages()
        wx.CallAfter(self.actions.register_tools)  # DEBUG

    def _subscribe_messages(self):
        for listener, topic in [
            (lambda msg: self.SetStatusText('Saved %s' % msg.path), RideSaved),
            (lambda msg: self.SetStatusText('Saved all files'), RideSaveAll),
            (self._set_label, RideTreeSelection),
            (self._show_validation_error, RideInputValidationError),
            (self._show_modification_prevented_error,
             RideModificationPrevented)
        ]:
            PUBLISHER.subscribe(listener, topic)

    def _set_label(self, message):
        self.SetTitle(self._create_title(message))

    def _create_title(self, message):
        title = 'RIDE'
        if message:
            item = message.item
            title += ' - ' + item.datafile.name
            if not item.is_modifiable():
                title += ' (READ ONLY)'
        return title

    def _show_validation_error(self, message):
        wx.MessageBox(message.message, 'Validation Error', style=wx.ICON_ERROR)

    def _show_modification_prevented_error(self, message):
        wx.MessageBox("\"%s\" is read only" %
                      message.controller.datafile_controller.filename,
                      "Modification prevented",
                      style=wx.ICON_ERROR)

    def _init_ui(self):
        # self._mgr.AddPane(wx.Panel(self), aui.AuiPaneInfo().CenterPane())
        ##### self.splitter = wx.SplitterWindow(self, style=wx.SP_LIVE_UPDATE)
        # self._mgr.AddPane(wx.Panel(self), aui.AuiPaneInfo().CenterPane())
        # set up default notebook style
        self._notebook_style = aui.AUI_NB_DEFAULT_STYLE | \
                               aui.AUI_NB_TAB_EXTERNAL_MOVE | wx.NO_BORDER
        # TODO self._notebook_theme = 0 (allow to select themes for notebooks)
        # self.notebook = NoteBook(self.splitter, self._application,
        #                         self._notebook_style)
        self.notebook = NoteBook(self, self._application, self._notebook_style)
        self._mgr.AddPane(
            self.notebook,
            aui.AuiPaneInfo().Name("notebook_editors").CenterPane().PaneBorder(
                False))
        ################ Test
        # self._mgr.AddPane(self.CreateTextCtrl(),
        #                   aui.AuiPaneInfo().Name("text_content").
        #                   CenterPane().Hide().MinimizeButton(True))
        #
        # self._mgr.AddPane(self.CreateHTMLCtrl(),
        #                   aui.AuiPaneInfo().Name("html_content").
        #                   CenterPane().Hide().MinimizeButton(True))
        #
        # self._mgr.AddPane(self.CreateNotebook(),
        #                   aui.AuiPaneInfo().Name("notebook_content").
        #                   CenterPane().PaneBorder(False))
        ####################
        # self._mgr.AddPane(self.CreateSizeReportCtrl(), aui.AuiPaneInfo().
        #                   Name("test1").Caption(
        #     "Pane Caption").Top().MinimizeButton(True))

        mb = MenuBar(self)
        self.toolbar = ToolBar(self)
        self.toolbar.SetMinSize(wx.Size(100, 60))
        # self.SetToolBar(self.toolbar.GetToolBar())
        self._mgr.AddPane(
            self.toolbar,
            aui.AuiPaneInfo().Name("maintoolbar").ToolbarPane().Top())
        self.actions = ActionRegisterer(self._mgr, mb, self.toolbar,
                                        ShortcutRegistry(self))
        """
        ##### Test
        tb3 = self.testToolbar()

        self._mgr.AddPane(tb3,
                          aui.AuiPaneInfo().Name("tb3").Caption("Toolbar 3").
                          ToolbarPane().Top().Row(1).Position(1))
        
        ##### End Test
        """
        # self._mgr.AddPane(self.CreateTreeControl(),
        #                  aui.AuiPaneInfo().Name("tree_content").
        #                  CenterPane().Hide().MinimizeButton(True))
        ###### self.tree = Tree(self.splitter, self.actions, self._application.settings)
        self.tree = Tree(self, self.actions, self._application.settings)
        #self.tree.SetMinSize(wx.Size(100, 200))
        self.tree.SetMinSize(wx.Size(120, 200))
        self._mgr.AddPane(
            self.tree,
            aui.AuiPaneInfo().Name("tree_content").Caption(
                "Test Suites").LeftDockable(True).CloseButton(False))
        # MaximizeButton(True).MinimizeButton(True))
        self.actions.register_actions(
            ActionInfoCollection(_menudata, self, self.tree))
        ###### File explorer pane
        self.filemgr = wx.GenericDirCtrl(self,
                                         -1,
                                         size=(200, 225),
                                         style=wx.DIRCTRL_3D_INTERNAL)
        self.filemgr.SetMinSize(wx.Size(120, 200))
        # wx.CallAfter(self.filemgr.SetPath(self.tree.get_selected_datafile()))
        self._mgr.AddPane(
            self.filemgr,
            aui.AuiPaneInfo().Name("file_manager").Caption(
                "Files").LeftDockable(True).CloseButton(True))

        mb.take_menu_bar_into_use()
        #### self.splitter.SetMinimumPaneSize(100)
        #### self.splitter.SplitVertically(self.tree, self.notebook, 300)
        self.CreateStatusBar()
        self.SetIcons(ImageProvider().PROGICONS)
        # tell the manager to "commit" all the changes just made
        self._mgr.Update()

    def testToolbar(self):

        #### More testing
        prepend_items, append_items = [], []
        item = aui.AuiToolBarItem()

        item.SetKind(wx.ITEM_SEPARATOR)
        append_items.append(item)

        item = aui.AuiToolBarItem()
        item.SetKind(wx.ITEM_NORMAL)
        item.SetId(ID_CustomizeToolbar)
        item.SetLabel("Customize...")
        append_items.append(item)

        tb3 = aui.AuiToolBar(self,
                             -1,
                             wx.DefaultPosition,
                             wx.DefaultSize,
                             agwStyle=aui.AUI_TB_DEFAULT_STYLE
                             | aui.AUI_TB_OVERFLOW)
        tb3.SetToolBitmapSize(wx.Size(16, 16))
        tb3_bmp1 = wx.ArtProvider.GetBitmap(wx.ART_FOLDER, wx.ART_OTHER,
                                            wx.Size(16, 16))
        tb3.AddSimpleTool(ID_SampleItem + 16, "Check 1", tb3_bmp1, "Check 1",
                          aui.ITEM_CHECK)
        tb3.AddSimpleTool(ID_SampleItem + 17, "Check 2", tb3_bmp1, "Check 2",
                          aui.ITEM_CHECK)
        tb3.AddSimpleTool(ID_SampleItem + 18, "Check 3", tb3_bmp1, "Check 3",
                          aui.ITEM_CHECK)
        tb3.AddSimpleTool(ID_SampleItem + 19, "Check 4", tb3_bmp1, "Check 4",
                          aui.ITEM_CHECK)
        tb3.AddSeparator()
        tb3.AddSimpleTool(ID_SampleItem + 20, "Radio 1", tb3_bmp1, "Radio 1",
                          aui.ITEM_RADIO)
        tb3.AddSimpleTool(ID_SampleItem + 21, "Radio 2", tb3_bmp1, "Radio 2",
                          aui.ITEM_RADIO)
        tb3.AddSimpleTool(ID_SampleItem + 22, "Radio 3", tb3_bmp1, "Radio 3",
                          aui.ITEM_RADIO)
        tb3.AddSeparator()
        tb3.AddSimpleTool(ID_SampleItem + 23, "Radio 1 (Group 2)", tb3_bmp1,
                          "Radio 1 (Group 2)", aui.ITEM_RADIO)
        tb3.AddSimpleTool(ID_SampleItem + 24, "Radio 2 (Group 2)", tb3_bmp1,
                          "Radio 2 (Group 2)", aui.ITEM_RADIO)
        tb3.AddSimpleTool(ID_SampleItem + 25, "Radio 3 (Group 2)", tb3_bmp1,
                          "Radio 3 (Group 2)", aui.ITEM_RADIO)

        tb3.SetCustomOverflowItems(prepend_items, append_items)
        tb3.Realize()
        return tb3

    def get_selected_datafile(self):
        return self.tree.get_selected_datafile()

    def get_selected_datafile_controller(self):
        return self.tree.get_selected_datafile_controller()

    def OnClose(self, event):
        if self._allowed_to_exit():
            PUBLISHER.unsubscribe(self._set_label, RideTreeSelection)
            RideClosing().publish()
            # deinitialize the frame manager
            self._mgr.UnInit()
            self.Destroy()
        else:
            wx.CloseEvent.Veto(event)

    def OnSize(self, event):
        if not self.IsMaximized():
            self._application.settings['mainframe maximized'] = False
            self._application.settings['mainframe size'] = self.DoGetSize()
        event.Skip()

    def OnMove(self, event):
        # When the window is Iconized, a move event is also raised, but we
        # don't want to update the position in the settings file
        if not self.IsIconized() and not self.IsMaximized():
            self._application.settings['mainframe position'] =\
                self.DoGetPosition()
        event.Skip()

    def OnMaximize(self, event):
        self._application.settings['mainframe maximized'] = True
        event.Skip()

    def OnReleasenotes(self, event):
        pass

    def _allowed_to_exit(self):
        if self.has_unsaved_changes():
            ret = wx.MessageBox(
                "There are unsaved modifications.\n"
                "Do you want to save your changes before "
                "exiting?", "Warning", wx.ICON_WARNING | wx.CANCEL | wx.YES_NO)
            if ret == wx.CANCEL:
                return False
            if ret == wx.YES:
                self.save()
        return True

    def has_unsaved_changes(self):
        return self._controller.is_dirty()

    def OnNewProject(self, event):
        if not self.check_unsaved_modifications():
            return
        NewProjectDialog(self._controller).execute()
        self._populate_tree()

    def _populate_tree(self):
        self.tree.populate(self._controller)
        if len(self._controller.data.directory) > 1:
            self.filemgr.SelectPath(self._controller.data.source)
            try:
                self.filemgr.ExpandPath(self._controller.data.source)
            except Exception:
                pass
            self.filemgr.Update()

    def OnOpenFile(self, event):
        if not self.filemgr:
            return
        # EVT_DIRCTRL_FILEACTIVATED
        from os.path import splitext
        robottypes = self._application.settings.get(
            'robot types', ['robot', 'resource'
                            'txt', 'tsv', 'html'])
        path = self.filemgr.GetFilePath()
        ext = ''
        if len(path) > 0:
            ext = splitext(path)
            ext = ext[1].replace('.', '')
            # print("DEBUG: path %s ext %s" % (path, ext))
        if len(ext) > 0 and ext in robottypes:
            if not self.check_unsaved_modifications():
                return
            if self.open_suite(path):
                return
        from robotide.editor import customsourceeditor
        customsourceeditor.main(path)

    def OnMenuOpenFile(self, event):
        if not self.filemgr:
            return
        # TODO: Use widgets/popupmenu tools
        path = self.filemgr.GetFilePath()
        if len(path) > 0:
            self.OnOpenFile(event)
        else:
            path = self.filemgr.GetPath()
            if not self.check_unsaved_modifications():
                return
            self.open_suite(path)  # It is a directory, do not edit
        event.Skip()

    def OnOpenTestSuite(self, event):
        if not self.check_unsaved_modifications():
            return
        path = RobotFilePathDialog(self, self._controller,
                                   self._application.settings).execute()
        if path:
            if self.open_suite(path):
                return
            from robotide.editor import customsourceeditor
            customsourceeditor.main(path)

    def check_unsaved_modifications(self):
        if self.has_unsaved_changes():
            ret = wx.MessageBox(
                "There are unsaved modifications.\n"
                "Do you want to proceed without saving?", "Warning",
                wx.ICON_WARNING | wx.YES_NO)
            return ret == wx.YES
        return True

    def open_suite(self, path):
        self._controller.update_default_dir(path)
        try:
            err = self._controller.load_datafile(path,
                                                 LoadProgressObserver(self))
        finally:
            if isinstance(err, UserWarning):
                # raise err  # Just leave message in Parser Log
                return False
        self._populate_tree()
        return True

    def refresh_datafile(self, item, event):
        self.tree.refresh_datafile(item, event)
        if self.filemgr:
            self.filemgr.ReCreateTree()

    def OnOpenDirectory(self, event):
        if self.check_unsaved_modifications():
            path = wx.DirSelector(message="Choose a directory containing Robot"
                                  " files",
                                  default_path=self._controller.default_dir)
            if path:
                self.open_suite(path)

    def OnSave(self, event):
        self.save()

    def OnSaveAll(self, event):
        self.save_all()

    def save_all(self):
        self._show_dialog_for_files_without_format()
        self._controller.execute(SaveAll())

    def save(self, controller=None):
        if controller is None:
            controller = self.get_selected_datafile_controller()
        if controller is not None:
            if not controller.has_format():
                self._show_dialog_for_files_without_format(controller)
            else:
                controller.execute(SaveFile())

    def _show_dialog_for_files_without_format(self, controller=None):
        files_without_format = self._controller.get_files_without_format(
            controller)
        for f in files_without_format:
            self._show_format_dialog_for(f)

    def _show_format_dialog_for(self, file_controller_without_format):
        InitFileFormatDialog(file_controller_without_format).execute()

    def OnExit(self, event):
        self.Close()

    def OnManagePlugins(self, event):
        self._plugin_manager.show(self._application.get_plugins())

    def OnViewAllTags(self, event):
        if self._view_all_tags_dialog is None:
            self._view_all_tags_dialog = ViewAllTagsDialog(
                self._controller, self)
        self._view_all_tags_dialog.show_dialog()

    def OnSearchUnusedKeywords(self, event):
        if self._review_dialog is None:
            self._review_dialog = ReviewDialog(self._controller, self)
        self._review_dialog.show_dialog()

    def OnPreferences(self, event):
        dlg = PreferenceEditor(self,
                               "RIDE - Preferences",
                               self._application.preferences,
                               style='tree')
        # I would prefer that this not be modal, but making it non-
        # modal opens up a can of worms. We don't want to have to deal
        # with settings getting changed out from under us while the
        # dialog is open.
        dlg.ShowModal()
        dlg.Destroy()

    def OnAbout(self, event):
        dlg = AboutDialog()
        dlg.ShowModal()
        dlg.Destroy()

    def OnShortcutkeys(self, event):
        dialog = ShortcutKeysDialog()
        dialog.Show()

    def OnReportaProblem(self, event):
        wx.LaunchDefaultBrowser("https://github.com/robotframework/RIDE/issues"
                                "?utf8=%E2%9C%93&q=is%3Aissue+%22search"
                                "%20your%20problem%22")

    def OnUserGuide(self, event):
        wx.LaunchDefaultBrowser("http://robotframework.org/robotframework/"
                                "#user-guide")

    def OnWiki(self, event):
        wx.LaunchDefaultBrowser("https://github.com/robotframework/RIDE/wiki")

    def _has_data(self):
        return self._controller.data is not None

    def _refresh(self):
        self._controller.update_namespace()

    # This code is copied from http://wiki.wxpython.org/EnsureFrameIsOnScreen,
    # and adapted to fit our code style.
    def ensure_on_screen(self):
        try:
            display_id = wx.Display.GetFromWindow(self)
        except NotImplementedError:
            display_id = 0
        if display_id == -1:
            display_id = 0
        geometry = wx.Display(display_id).GetGeometry()
        position = self.GetPosition()
        if position.x < geometry.x:
            position.x = geometry.x
        if position.y < geometry.y:
            position.y = geometry.y
        size = self.GetSize()
        if size.width > geometry.width:
            size.width = geometry.width
            position.x = geometry.x
        elif position.x + size.width > geometry.x + geometry.width:
            position.x = geometry.x + geometry.width - size.width
        if size.height > geometry.height:
            size.height = geometry.height
            position.y = geometry.y
        elif position.y + size.height > geometry.y + geometry.height:
            position.y = geometry.y + geometry.height - size.height
        self.SetPosition(position)
        self.SetSize(size)

    # DEBUG just some testing
    def CreateSizeReportCtrl(self, width=80, height=80):

        ctrl = SizeReportCtrl(self, -1, wx.DefaultPosition,
                              wx.Size(width, height), self._mgr)
        return ctrl
Beispiel #4
0
class ListEditor(ListEditorBase, RideEventHandler):
    __metaclass__ = classmaker()
    pass
Beispiel #5
0
class RideFrame(wx.Frame, RideEventHandler):
    __metaclass__ = classmaker()

    def __init__(self, application, controller):
        wx.Frame.__init__(self,
                          parent=None,
                          title='RIDE',
                          pos=application.settings.get('mainframe position',
                                                       (50, 30)),
                          size=application.settings.get(
                              'mainframe size', (1100, 700)))
        self.ensure_on_screen()
        if application.settings.get('mainframe maximized', False):
            self.Maximize()
        self._application = application
        self._controller = controller
        self._init_ui()
        self._plugin_manager = PluginManager(self.notebook)
        self._review_dialog = None
        self._view_all_tags_dialog = None
        self.Bind(wx.EVT_CLOSE, self.OnClose)
        self.Bind(wx.EVT_SIZE, self.OnSize)
        self.Bind(wx.EVT_MOVE, self.OnMove)
        self.Bind(wx.EVT_MAXIMIZE, self.OnMaximize)
        self._subscribe_messages()
        # print("DEBUG: Call Show")
        self.Show()
        #print("DEBUG: Call register_tools, actions: %s" % self.actions.__repr__())
        if PY2:
            wx.CallLater(100, self.actions.register_tools)  # DEBUG
        else:
            wx.CallAfter(self.actions.register_tools)  # DEBUG

    def _subscribe_messages(self):
        for listener, topic in [
            (lambda msg: self.SetStatusText('Saved %s' % msg.path), RideSaved),
            (lambda msg: self.SetStatusText('Saved all files'), RideSaveAll),
            (self._set_label, RideTreeSelection),
            (self._show_validation_error, RideInputValidationError),
            (self._show_modification_prevented_error,
             RideModificationPrevented)
        ]:
            PUBLISHER.subscribe(listener, topic)

    def _set_label(self, message):
        self.SetTitle(self._create_title(message))

    def _create_title(self, message):
        title = 'RIDE'
        if message:
            item = message.item
            title += ' - ' + item.datafile.name
            if not item.is_modifiable():
                title += ' (READ ONLY)'
        return title

    def _show_validation_error(self, message):
        wx.MessageBox(message.message, 'Validation Error', style=wx.ICON_ERROR)

    def _show_modification_prevented_error(self, message):
        wx.MessageBox("\"%s\" is read only" %
                      message.controller.datafile_controller.filename,
                      "Modification prevented",
                      style=wx.ICON_ERROR)

    def _init_ui(self):
        splitter = wx.SplitterWindow(self, style=wx.SP_LIVE_UPDATE)
        self.notebook = NoteBook(splitter, self._application)
        mb = MenuBar(self)
        self.toolbar = ToolBar(self)
        self.actions = ActionRegisterer(mb, self.toolbar,
                                        ShortcutRegistry(self))
        self.tree = Tree(splitter, self.actions, self._application.settings)
        self.actions.register_actions(
            ActionInfoCollection(_menudata, self, self.tree))
        mb.take_menu_bar_into_use()
        splitter.SetMinimumPaneSize(100)
        splitter.SplitVertically(self.tree, self.notebook, 300)
        self.CreateStatusBar()
        self.SetIcons(ImageProvider().PROGICONS)

    def get_selected_datafile(self):
        return self.tree.get_selected_datafile()

    def get_selected_datafile_controller(self):
        return self.tree.get_selected_datafile_controller()

    def OnClose(self, event):
        if self._allowed_to_exit():
            PUBLISHER.unsubscribe(self._set_label, RideTreeSelection)
            RideClosing().publish()
            self.Destroy()
        else:
            wx.CloseEvent.Veto(event)

    def OnSize(self, event):
        if not self.IsMaximized():
            self._application.settings['mainframe maximized'] = False
            self._application.settings['mainframe size'] = self.MyGetSize()
            # DEBUG wxPhoenix .GetSizeTuple()
        event.Skip()

    def OnMove(self, event):
        # When the window is Iconized, a move event is also raised, but we
        # don't want to update the position in the settings file
        if not self.IsIconized() and not self.IsMaximized():
            # DEBUG wxPhoenix writes wx.Point(50, 30) instead of just (50, 30)
            self._application.settings['mainframe position'] = \
                self.MyGetPosition()
            # DEBUG wxPhoenix self.GetPositionTuple()
        event.Skip()

    def OnMaximize(self, event):
        self._application.settings['mainframe maximized'] = True
        event.Skip()

    def OnReleasenotes(self, event):
        pass

    def MyGetSize(self):
        if wx.VERSION >= (3, 0, 3, ''):  # DEBUG wxPhoenix
            return self.DoGetSize()
        else:
            return self.GetSizeTuple()

    def MyGetPosition(self):
        if wx.VERSION >= (3, 0, 3, ''):  # DEBUG wxPhoenix
            return self.DoGetPosition()
        else:
            return self.GetPositionTuple()

    def _allowed_to_exit(self):
        if self.has_unsaved_changes():
            ret = wx.MessageBox(
                "There are unsaved modifications.\n"
                "Do you want to save your changes before "
                "exiting?", "Warning", wx.ICON_WARNING | wx.CANCEL | wx.YES_NO)
            if ret == wx.CANCEL:
                return False
            if ret == wx.YES:
                self.save()
        return True

    def has_unsaved_changes(self):
        return self._controller.is_dirty()

    def OnNewProject(self, event):
        if not self.check_unsaved_modifications():
            return
        NewProjectDialog(self._controller).execute()
        self._populate_tree()

    def _populate_tree(self):
        self.tree.populate(self._controller)

    def OnOpenTestSuite(self, event):
        if not self.check_unsaved_modifications():
            return
        path = RobotFilePathDialog(self, self._controller,
                                   self._application.settings).execute()
        if path:
            self.open_suite(path)

    def check_unsaved_modifications(self):
        if self.has_unsaved_changes():
            ret = wx.MessageBox(
                "There are unsaved modifications.\n"
                "Do you want to proceed without saving?", "Warning",
                wx.ICON_WARNING | wx.YES_NO)
            return ret == wx.YES
        return True

    def open_suite(self, path):
        self._controller.update_default_dir(path)
        self._controller.load_datafile(path, LoadProgressObserver(self))
        self._populate_tree()

    def refresh_datafile(self, item, event):
        self.tree.refresh_datafile(item, event)

    def OnOpenDirectory(self, event):
        if self.check_unsaved_modifications():
            if wx.VERSION >= (3, 0, 3, ''):  # DEBUG wxPhoenix
                path = wx.DirSelector(
                    message="Choose a directory containing "
                    "Robot files",
                    default_path=self._controller.default_dir)
            else:
                path = wx.DirSelector(message="Choose a directory containing "
                                      "Robot files",
                                      defaultPath=self._controller.default_dir)
            if path:
                self.open_suite(path)

    def OnSave(self, event):
        self.save()

    def OnSaveAll(self, event):
        self.save_all()

    def save_all(self):
        self._show_dialog_for_files_without_format()
        self._controller.execute(SaveAll())

    def save(self, controller=None):
        if controller is None:
            controller = self.get_selected_datafile_controller()
        if controller is not None:
            if not controller.has_format():
                self._show_dialog_for_files_without_format(controller)
            else:
                controller.execute(SaveFile())

    def _show_dialog_for_files_without_format(self, controller=None):
        files_without_format = self._controller.get_files_without_format(
            controller)
        for f in files_without_format:
            self._show_format_dialog_for(f)

    def _show_format_dialog_for(self, file_controller_without_format):
        InitFileFormatDialog(file_controller_without_format).execute()

    def OnExit(self, event):
        self.Close()

    def OnManagePlugins(self, event):
        self._plugin_manager.show(self._application.get_plugins())

    def OnViewAllTags(self, event):
        if self._view_all_tags_dialog is None:
            self._view_all_tags_dialog = ViewAllTagsDialog(
                self._controller, self)
        self._view_all_tags_dialog.show_dialog()

    def OnSearchUnusedKeywords(self, event):
        if self._review_dialog is None:
            self._review_dialog = ReviewDialog(self._controller, self)
        self._review_dialog.show_dialog()

    def OnPreferences(self, event):
        dlg = PreferenceEditor(self,
                               "RIDE - Preferences",
                               self._application.preferences,
                               style='tree')
        # I would prefer that this not be modal, but making it non-
        # modal opens up a can of worms. We don't want to have to deal
        # with settings getting changed out from under us while the
        # dialog is open.
        dlg.ShowModal()
        dlg.Destroy()

    def OnAbout(self, event):
        dlg = AboutDialog()
        dlg.ShowModal()
        dlg.Destroy()

    def OnShortcutkeys(self, event):
        dialog = ShortcutKeysDialog()
        dialog.Show()

    def OnReportaProblem(self, event):
        wx.LaunchDefaultBrowser(
            "https://github.com/robotframework/RIDE/issues")

    def OnUserGuide(self, event):
        wx.LaunchDefaultBrowser("http://robotframework.org/robotframework/"
                                "#user-guide")

    def _has_data(self):
        return self._controller.data is not None

    def _refresh(self):
        self._controller.update_namespace()


# This code is copied from http://wiki.wxpython.org/EnsureFrameIsOnScreen,
# and adapted to fit our code style.

    def ensure_on_screen(self):
        try:
            display_id = wx.Display.GetFromWindow(self)
        except NotImplementedError:
            display_id = 0
        if display_id == -1:
            display_id = 0
        geometry = wx.Display(display_id).GetGeometry()
        position = self.GetPosition()
        if position.x < geometry.x:
            position.x = geometry.x
        if position.y < geometry.y:
            position.y = geometry.y
        size = self.GetSize()
        if size.width > geometry.width:
            size.width = geometry.width
            position.x = geometry.x
        elif position.x + size.width > geometry.x + geometry.width:
            position.x = geometry.x + geometry.width - size.width
        if size.height > geometry.height:
            size.height = geometry.height
            position.y = geometry.y
        elif position.y + size.height > geometry.y + geometry.height:
            position.y = geometry.y + geometry.height - size.height
        self.SetPosition(position)
        self.SetSize(size)
class SettingEditor(
        with_metaclass(classmaker(), wx.Panel, utils.RideEventHandler)):
    def __init__(self, parent, controller, plugin, tree):
        wx.Panel.__init__(self, parent)
        self._controller = controller
        self.plugin = plugin
        self._datafile = controller.datafile
        self._create_controls()
        self._tree = tree
        self._editing = False
        self.plugin.subscribe(self.update_value, RideImportSetting)

    def _create_controls(self):
        sizer = wx.BoxSizer(wx.HORIZONTAL)
        sizer.Add((5, 0))
        sizer.Add(
            Label(self,
                  label=self._controller.label,
                  size=(context.SETTING_LABEL_WIDTH,
                        context.SETTING_ROW_HEIGTH)))
        self._value_display = self._create_value_display()
        self.update_value()
        self._tooltip = self._get_tooltip()
        sizer.Add(self._value_display, 1, wx.EXPAND)
        self._add_edit(sizer)
        sizer.Add(ButtonWithHandler(self, 'Clear'))
        sizer.Layout()
        self.SetSizer(sizer)

    def _add_edit(self, sizer):
        sizer.Add(ButtonWithHandler(self, 'Edit'),
                  flag=wx.LEFT | wx.RIGHT,
                  border=5)

    def _create_value_display(self):
        display = self._value_display_control()
        display.Bind(wx.EVT_ENTER_WINDOW, self.OnEnterWindow)
        display.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveWindow)
        display.Bind(wx.EVT_WINDOW_DESTROY, self.OnWindowDestroy)
        display.Bind(wx.EVT_MOTION, self.OnDisplayMotion)
        return display

    def _value_display_control(self):
        ctrl = SettingValueDisplay(self)
        ctrl.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
        ctrl.Bind(wx.EVT_KEY_DOWN, self.OnKey)
        return ctrl

    def _get_tooltip(self):
        return HtmlPopupWindow(self, (500, 350))

    def OnKey(self, event):
        self._tooltip.hide()
        event.Skip()

    def OnDisplayMotion(self, event):
        self._tooltip.hide()

    def refresh(self, controller):
        self._controller = controller
        self.update_value()

    def refresh_datafile(self, item, event):
        self._tree.refresh_datafile(item, event)

    def OnEdit(self, event=None):
        self._hide_tooltip()
        self._editing = True
        dlg = self._crete_editor_dialog()
        if dlg.ShowModal() == wx.ID_OK:
            self._set_value(dlg.get_value(), dlg.get_comment())
            self._update_and_notify()
        dlg.Destroy()
        self._editing = False

    def _crete_editor_dialog(self):
        dlg_class = EditorDialog(self._controller)
        return dlg_class(self._datafile, self._controller, self.plugin)

    def _set_value(self, value_list, comment):
        self._controller.execute(SetValues(value_list, comment))

    def _hide_tooltip(self):
        self._stop_popup_timer()
        self._tooltip.hide()

    def _stop_popup_timer(self):
        if hasattr(self, 'popup_timer'):
            self.popup_timer.Stop()

    def OnEnterWindow(self, event):
        if self._mainframe_has_focus():
            self.popup_timer = wx.CallLater(500, self.OnPopupTimer, event)

    def _mainframe_has_focus(self):
        return wx.GetTopLevelParent(self.FindFocus()) == \
            wx.GetTopLevelParent(self)

    def OnWindowDestroy(self, event):
        self._stop_popup_timer()
        self._tooltip.hide()
        event.Skip()

    def OnLeaveWindow(self, event):
        self._stop_popup_timer()
        self._tooltip.hide()
        event.Skip()

    def OnPopupTimer(self, event):
        _tooltipallowed = False
        # TODO This prevents tool tip for ex. Template edit field in wxPhoenix
        try:  # DEBUG wxPhoenix
            _tooltipallowed = self.Parent.tooltip_allowed(self._tooltip)
        #_tooltipallowed = self._get_tooltip()
        except AttributeError:
            # print("DEBUG: There was an attempt to show a Tool Tip.\n")
            pass
        if _tooltipallowed:
            details, title = self._get_details_for_tooltip()
            if details:
                self._tooltip.set_content(details, title)
                self._tooltip.show_at(self._tooltip_position())

    def _get_details_for_tooltip(self):
        kw = self._controller.keyword_name
        return self.plugin.get_keyword_details(kw), kw

    def _tooltip_position(self):
        ms = wx.GetMouseState()
        # ensure that the popup gets focus immediately
        return ms.x - 3, ms.y - 3

    def OnLeftUp(self, event):
        if event.ControlDown() or event.CmdDown():
            self._navigate_to_user_keyword()
        else:
            if self._has_selected_area() and not self._editing:
                wx.CallAfter(self.OnEdit, event)
            event.Skip()

    def _has_selected_area(self):
        selection = self._value_display.GetSelection()
        if selection is None:
            return False
        return selection[0] == selection[1]

    def _navigate_to_user_keyword(self):
        uk = self.plugin.get_user_keyword(self._controller.keyword_name)
        if uk:
            self._tree.select_user_keyword_node(uk)

    def _update_and_notify(self):
        self.update_value()

    def OnClear(self, event):
        self._controller.execute(ClearSetting())
        self._update_and_notify()

    def update_value(self, event=None):
        if self._controller is None:
            return
        if self._controller.is_set:
            self._value_display.set_value(self._controller, self.plugin)
        else:
            self._value_display.clear()

    def get_selected_datafile_controller(self):
        return self._controller.datafile_controller

    def close(self):
        self._controller = None
        self.plugin.unsubscribe(self.update_value, RideImportSetting)

    def highlight(self, text):
        return self._value_display.highlight(text)

    def clear_highlight(self):
        return self._value_display.clear_highlight()

    def contains(self, text):
        return self._value_display.contains(text)
Beispiel #7
0
class ListEditor(with_metaclass(classmaker(), ListEditorBase,
                                RideEventHandler)):
    pass