import os import logging import gettext import gi import time import db_interaction gi.require_version('Gtk', '3.0') gi.require_version('GtkSource', '3.0') from gi.repository import Gtk, Gdk, GtkSource, GdkPixbuf # ------------------------------------------- import data_model import dnd_data_parser import toolbox import cairo import sys import confignator ccs_path = confignator.get_option('paths', 'ccs') sys.path.append(ccs_path) import ccs_function_lib as cfl lm = GtkSource.LanguageManager() lngg = lm.get_language('python') logger = logging.getLogger(__name__) logger.setLevel(level=logging.WARNING) console_hdlr = toolbox.create_console_handler() logger.addHandler(hdlr=console_hdlr) # using gettext for internationalization (i18n) localedir = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'locale') translate = gettext.translation('handroll', localedir, fallback=True) _ = translate.gettext style_path = os.path.join(os.path.dirname(__file__), 'style') widget_grip_upper = 1 / 4 widget_grip_lower = 3 / 4 class Board(Gtk.Box): """ A sketch desk consists out of a toolbar and a grid where the StepWidgets are placed. Features * data model functions * change of the number of a step triggers renumbering and reordering of all steps * drag and drop * reorder the steps * drag the 'Add step' button and insert a new step * drop a step before or after a existing step. Highlight the associated border * select multiple steps and move them as a block * adding a parallel thread * place the start before, within or after an existing step * place the end before, within or after an existing step * loop over steps * select multiple steps add loop over them till a break condition is reached (ToDo) """ def __init__(self, model, app, filename=None, logger=logger): """ When an instance is initialized: * a data model instance is created * the toolbar is created * a instance of a grid is added * drag and drop is set up """ # assert isinstance(model, data_model.TestSpecification) self.model = model self.app = app self._filename = filename self.logger = logger self._test_is_locked = None # Save Button in TST clicked for first time, Always do save_as to not overwrite something, used in tst.py self._ask_overwrite = True # Gtk.Box.__init__(self) super(Board, self).__init__() self.set_orientation(Gtk.Orientation.VERTICAL) # test meta data self.test_meta_data_box = Gtk.Box(margin=5) self.test_meta_data_box.set_orientation(Gtk.Orientation.HORIZONTAL) self.test_meta_data_info = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) self.test_meta_data_pre_post_con = Gtk.Box(margin_start=10) self.test_meta_data_pre_post_con.set_orientation(Gtk.Orientation.VERTICAL) self.test_meta_data_pre_post_con_edit = Gtk.Box() self.test_meta_data_pre_post_con_edit.set_orientation(Gtk.Orientation.VERTICAL) # name of the test self.test_meta_data_name_label = Gtk.Label() self.test_meta_data_name_label.set_text('Name of the test:') self.test_meta_data_name = Gtk.Entry(width_chars=25) self.test_meta_data_name.set_placeholder_text('< name of the test >') self.test_meta_data_name_box = Gtk.Box(spacing=5, orientation=Gtk.Orientation.HORIZONTAL) self.test_meta_data_name_box.pack_start(self.test_meta_data_name_label, False, False, 4) self.test_meta_data_name_box.pack_end(self.test_meta_data_name, False, False, 0) self.test_meta_data_info.pack_start(self.test_meta_data_name_box, True, True, 0) # test description self.test_meta_data_desc_label = Gtk.Label() self.test_meta_data_desc_label.set_text('Short description:') self.test_meta_data_desc = Gtk.Entry(width_chars=25) self.test_meta_data_desc.set_placeholder_text('< description of the test >') self.test_meta_data_desc_box = Gtk.Box(spacing=5, orientation=Gtk.Orientation.HORIZONTAL) self.test_meta_data_desc_box.pack_start(self.test_meta_data_desc_label, False, False, 4) self.test_meta_data_desc_box.pack_end(self.test_meta_data_desc, False, False, 0) self.test_meta_data_info.pack_start(self.test_meta_data_desc_box, True, True, 0) # spec_version self.test_meta_data_spec_version_label = Gtk.Label() self.test_meta_data_spec_version_label.set_text('Spec. version:') self.test_meta_data_spec_version = Gtk.Entry(width_chars=25) self.test_meta_data_spec_version.set_placeholder_text('< spec version >') self.test_meta_data_spec_box = Gtk.Box(spacing=5, orientation=Gtk.Orientation.HORIZONTAL) self.test_meta_data_spec_box.pack_start(self.test_meta_data_spec_version_label, False, False, 4) self.test_meta_data_spec_box.pack_end(self.test_meta_data_spec_version, False, False, 0) self.test_meta_data_info.pack_start(self.test_meta_data_spec_box, True, True, 0) # IASW Software Version self.test_meta_data_iasw_version_label = Gtk.Label() self.test_meta_data_iasw_version_label.set_text('IASW version:') self.test_meta_data_iasw_version = Gtk.Entry(width_chars=25) self.test_meta_data_iasw_version.set_placeholder_text('< IASW version >') self.test_meta_data_iasw_box = Gtk.Box(spacing=5, orientation=Gtk.Orientation.HORIZONTAL) self.test_meta_data_iasw_box.pack_start(self.test_meta_data_iasw_version_label, False, False, 4) self.test_meta_data_iasw_box.pack_end(self.test_meta_data_iasw_version, False, False, 0) self.test_meta_data_info.pack_start(self.test_meta_data_iasw_box, True, True, 0) # IASW Software Version self.test_meta_data_req_label = Gtk.Label() self.test_meta_data_req_label.set_text('Requirements:') self.test_meta_data_req = Gtk.Entry(width_chars=25) self.test_meta_data_req.set_placeholder_text('< Requirements >') self.test_meta_data_req_box = Gtk.Box(spacing=5, orientation=Gtk.Orientation.HORIZONTAL) self.test_meta_data_req_box.pack_start(self.test_meta_data_req_label, False, False, 4) self.test_meta_data_req_box.pack_end(self.test_meta_data_req, False, False, 0) self.test_meta_data_info.pack_start(self.test_meta_data_req_box, True, True, 0) # checkbox for locking the step numbers self.test_is_locked_label = Gtk.Label() self.test_is_locked_label.set_text(_('Lock step enumeration:')) self.text_meta_data_test_is_locked = Gtk.CheckButton() self.test_meta_data_is_locked_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) self.test_meta_data_is_locked_box.pack_start(self.test_is_locked_label, False, False, 4) self.test_meta_data_is_locked_box.pack_start(self.text_meta_data_test_is_locked, True, True, 4) self.test_meta_data_info.pack_start(self.test_meta_data_is_locked_box, True, True, 15) # Add pre post condition selections # Pre conditions self.precon_selection_label = Gtk.Label() self.precon_selection_label.set_text('Pre-conditions:') self.precon_selection = Gtk.ComboBoxText() self.set_precon_model() self.precon_selection.connect("changed", self.on_precon_changed) # Post conditions self.postcon_selection_label = Gtk.Label() self.postcon_selection_label.set_text('Post-conditions:') self.postcon_selection = Gtk.ComboBoxText() self.set_postcon_model() self.postcon_selection.connect("changed", self.on_postcon_changed) # Add Edit Buttons self.precon_edit_button = Gtk.Button.new_with_label('Edit') self.precon_edit_button.connect("clicked", self.precon_edit_clicked) self.postcon_edit_button = Gtk.Button.new_with_label('Edit') self.postcon_edit_button.connect("clicked", self.postcon_edit_clicked) precon_line = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) precon_line.pack_start(self.precon_selection, False, True, 0) precon_line.pack_start(self.precon_edit_button, False, True, 0) postcon_line = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) postcon_line.pack_start(self.postcon_selection, False, True, 0) postcon_line.pack_start(self.postcon_edit_button, False, True, 0) # add to pre post box self.test_meta_data_pre_post_con.pack_start(self.precon_selection_label, False, True, 0) self.test_meta_data_pre_post_con.pack_start(precon_line, False, True, 2) self.test_meta_data_pre_post_con.pack_start(self.postcon_selection_label, False, True, 2) self.test_meta_data_pre_post_con.pack_start(postcon_line, False, True, 0) self.test_meta_data_box.set_spacing(10) self.test_comment_box = Gtk.Box(spacing=2, margin_end=5) self.test_comment_box.set_orientation(Gtk.Orientation.VERTICAL) self.label_comment = Gtk.Label() self.label_comment.set_halign(Gtk.Align.START) self.label_comment.set_text(_('Test comment:')) # Make the area where the real command is entered self.comment_scrolled_window = Gtk.ScrolledWindow() self.comment_scrolled_window.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) # self.comment_scrolled_window.set_size_request(200, 100) self.test_meta_data_comment = Gtk.TextView.new() self.test_meta_data_comment.set_wrap_mode(Gtk.WrapMode.WORD) Gtk.StyleContext.add_class(self.test_meta_data_comment.get_style_context(), 'text-view') self.comment_scrolled_window.add(self.test_meta_data_comment) self.test_comment_box.pack_start(self.label_comment, False, False, 0) self.test_comment_box.pack_start(self.comment_scrolled_window, True, True, 0) # add the meta data self.test_meta_data_box.pack_start(self.test_meta_data_info, False, True, 0) self.test_meta_data_box.pack_start(self.test_meta_data_pre_post_con, False, True, 0) self.test_meta_data_box.pack_start(self.test_meta_data_pre_post_con_edit, False, True, 0) self.test_meta_data_box.pack_start(self.test_comment_box, True, True, 0) self.pack_start(self.test_meta_data_box, False, True, 0) # add a custom init code block self.custom_import_box = Gtk.Box(spacing=5, margin_start=10, margin_end=10) self.custom_import_box.set_orientation(Gtk.Orientation.VERTICAL) self.bar_custom_import = Gtk.Box() self.bar_custom_import.set_orientation(Gtk.Orientation.HORIZONTAL) self.bar_custom_import.set_tooltip_text('Statements that are executed at the beginning of a test.\n' 'Define imports and variables used throughout the test here.') self.label_custom_import = Gtk.Label() self.label_custom_import.set_halign(Gtk.Align.START) self.label_custom_import.set_markup('<b>Init code</b>') self.button_custom_import = Gtk.ToolButton() self.button_custom_import.set_icon_name('pan-down-symbolic') self.button_custom_import.connect('clicked', self.on_init_code_toggle) self.bar_custom_import.pack_start(self.button_custom_import, False, True, 0) self.bar_custom_import.pack_start(self.label_custom_import, False, True, 0) self.custom_import_scrolled_window = Gtk.ScrolledWindow() self.custom_import_scrolled_window.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) self.custom_import_scrolled_window.set_size_request(-1, 200) self.test_custom_import = GtkSource.View() self.test_custom_import.set_auto_indent(True) self.test_custom_import.set_wrap_mode(Gtk.WrapMode.WORD) self.test_custom_import.set_show_line_numbers(True) self.test_custom_import.set_monospace(True) self.test_custom_import.set_highlight_current_line(True) self.test_custom_import.set_indent_on_tab(True) self.test_custom_import.set_insert_spaces_instead_of_tabs(True) self.test_custom_import.set_indent_width(4) self.test_custom_import.set_auto_indent(True) self.custom_import_buffer = self.test_custom_import.get_buffer() self.custom_import_buffer.set_language(lngg) # self.custom_import_buffer.set_style_scheme(self.board.current_scheme) self.custom_import_scrolled_window.add(self.test_custom_import) self.custom_import_box.pack_start(self.bar_custom_import, False, False, 0) self.custom_import_box.pack_start(self.custom_import_scrolled_window, True, True, 0) self.pack_start(self.custom_import_box, False, True, 0) # making the toolbar self.btn_add_step = Gtk.ToolButton() self.btn_add_step.set_label(_('Add step')) self.btn_add_step.set_tooltip_text(_('Add step')) self.btn_add_step.set_icon_name('list-add') self.btn_add_step.connect('clicked', self.on_btn_clicked_add_step) self.btn_collapse_all_steps = Gtk.ToolButton() self.btn_collapse_all_steps.set_label(_('Collapse all steps')) self.btn_collapse_all_steps.set_tooltip_text(_('Collapse all steps')) pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(ccs_path + '/pixmap/collapse.svg', 24, 24) icon = Gtk.Image.new_from_pixbuf(pixbuf) self.btn_collapse_all_steps.set_icon_widget(icon) self.btn_collapse_all_steps.connect('clicked', self.collapse_all_steps) self.btn_expand_all_steps = Gtk.ToolButton() self.btn_expand_all_steps.set_label(_('Expand all steps')) self.btn_expand_all_steps.set_tooltip_text(_('Expand all steps')) pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(ccs_path + '/pixmap/expand.svg', 24, 24) icon = Gtk.Image.new_from_pixbuf(pixbuf) self.btn_expand_all_steps.set_icon_widget(icon) self.btn_expand_all_steps.connect('clicked', self.expand_all_steps) # self.btn_add_parallel = Gtk.ToolButton() # self.btn_add_parallel.set_label(_('Add parallel sequence')) # self.btn_add_parallel.connect('clicked', self.on_btn_clicked_add_parallel) self.toolbar = Gtk.Toolbar() self.toolbar.insert(self.btn_add_step, 0) self.toolbar.insert(self.btn_collapse_all_steps, 1) self.toolbar.insert(self.btn_expand_all_steps, 2) # self.toolbar.insert(self.btn_add_parallel, 3) self.pack_start(self.toolbar, False, True, 0) # add the grid for steps self.scrolled_window = Gtk.ScrolledWindow() self.grid = Gtk.Grid() # self.grid.set_row_spacing(20) self.scrolled_window.add(self.grid) self.pack_start(self.scrolled_window, True, True, 0) # scheme for the GtkSource Views self.style_manager = GtkSource.StyleSchemeManager.get_default() self.style_manager.append_search_path(style_path) self.current_scheme = self.style_manager.get_scheme('darcula') # connect signals self.test_meta_data_name.connect('changed', self.on_test_name_change) self.test_meta_data_desc.connect('changed', self.on_test_desc_change) self.test_meta_data_spec_version.connect('changed', self.on_test_spec_version_change) self.test_meta_data_iasw_version.connect('changed', self.on_test_iasw_version_change) self.text_meta_data_test_is_locked.connect('toggled', self.on_test_locked_toggled) self.test_meta_data_comment.get_buffer().connect('changed', self.on_comment_change) self.custom_import_buffer.connect('changed', self.on_custom_import_change) Gtk.StyleContext.add_class(self.get_style_context(), 'board') @property def test_is_locked(self): return self._test_is_locked @test_is_locked.setter def test_is_locked(self, value): assert isinstance(value, bool) self._test_is_locked = value @property def filename(self): return self._filename @filename.setter def filename(self, value): self._filename = value @property def ask_overwrite(self): return self._ask_overwrite @ask_overwrite.setter def ask_overwrite(self, value): self._ask_overwrite = value def update_widget_data(self): """ Updates the grid with the steps. All widgets of the grid are destroyed. The grid is build new from the model. """ # update the meta data of the test spec self.set_test_spec_metadata() # update the steps grid self.destroy_all_step_widgets() self.build_all_steps() # refresh all widgets self.show_all() def set_test_spec_metadata(self): """ Update the displayed data of the test specification using the data from the model """ # set the name of the test specification using the data from the model self.test_meta_data_name.set_text(self.model.name) # set the description of the test specification using the data from the model self.test_meta_data_desc.set_text(self.model.description) # set the Specification version of the test specification from the data model self.test_meta_data_spec_version.set_text(self.model.spec_version) # set the Software version of the test specification from the data model self.test_meta_data_iasw_version.set_text(self.model.iasw_version) # set the pre-condition name if self.model.precon_name: found = False for index, precon_name in enumerate(self.precon_selection.get_model()): if precon_name[0] == self.model.precon_name: found = True self.precon_selection.set_active(index) if not found: msg = 'Given Pre-Condition Name could not be found/loaded' self.logger.warning(msg) # self.app.add_info_bar(message_type=Gtk.MessageType.INFO, message=msg) self.on_precon_changed(self.precon_selection) # set the post-condition name if self.model.postcon_name: found = False for index, postcon_name in enumerate(self.postcon_selection.get_model()): if postcon_name[0] == self.model.postcon_name: found = True self.postcon_selection.set_active(index) if not found: msg = 'Given Post-Condition Name could not be found/loaded' self.logger.warning(msg) # self.app.add_info_bar(message_type=Gtk.MessageType.INFO, message=msg) self.on_postcon_changed(self.precon_selection) # Set the test comment self.test_meta_data_comment.get_buffer().set_text(self.model.comment) # Set the Locked STep numeration self.text_meta_data_test_is_locked.set_active(self.model.primary_counter_locked) # Set the init code block self.custom_import_buffer.set_text(self.model.custom_imports) def on_init_code_toggle(self, widget): if self.custom_import_scrolled_window.is_visible(): self.custom_import_scrolled_window.set_visible(False) widget.set_icon_name('pan-end-symbolic') else: self.custom_import_scrolled_window.set_visible(True) widget.set_icon_name('pan-down-symbolic') def collapse_all_steps(self, button): """ Close all expander of the steps """ steps = self.grid.get_children() for child in steps: child.step_detail_visible = False def expand_all_steps(self, button): """ Expand all step widgets """ steps = self.grid.get_children() for child in steps: child.step_detail_visible = True def build_step(self, step_number): """ Create a new step widget and add it to the grid :param: step_number: number of a step """ step_to_add = StepWidget(model=self.model, step_number=step_number, app=self.app, board=self, logger=self.logger) # TODO: seq_num parameter?? # find the next free grid cell number_of_existing_steps = len(self.view.grid.get_children()) row = number_of_existing_steps - 1 self.grid.attach(step_to_add, 0, row, 1, 1) def on_btn_clicked_add_step(self, button): """ add a step to the model, then add a StepWidget to the steps grid and update the model viewer :param: button: button widget which was clicked """ # ToDo # self.model.get_sequence(self.sequence)add_step_below() self.model.get_sequence(0).add_step_below() # ToDo self.update_widget_data() self.app.update_model_viewer() def on_btn_clicked_add_parallel(self, button): """ Add the first step of a new sequence. Add the step to start the sequence. :param: button: Gtk.Button widget which was clicked """ # create the new sequence in the data model new_seq_num = self.model.add_sequence() # create the step to start the sequence (only the 1st sequence can start further ones) new_step = data_model.create_start_sequence_step(new_seq_num) self.model.get_sequence(0).add_step_below(step_instance=new_step) # create first step of the new sequence self.model.get_sequence(new_seq_num).add_step_below() # update the view self.update_widget_data() self.app.update_model_viewer() def set_precon_model(self, active_name=None): section_dict = db_interaction.get_pre_post_con(None) active_nbr = 0 for count, condition in enumerate(section_dict): self.precon_selection.append_text(condition.name) if active_name == condition.name: active_nbr = count self.precon_selection.set_active(active_nbr) self.on_precon_changed(self.precon_selection) return def on_precon_changed(self, widget): # get the name out of the widget precon_name = widget.get_active_text() # update the model self.model.precon_name = precon_name # Set the Precon Description section_dict = db_interaction.get_pre_post_con(None) for condition in section_dict: if condition.name == precon_name: self.model.precon_descr = condition.description self.model.precon_code = condition.condition # update the data model viewer self.app.update_model_viewer() def set_postcon_model(self, active_name=None): section_dict = db_interaction.get_pre_post_con(None) active_nbr = 0 for count, condition in enumerate(section_dict): self.postcon_selection.append_text(condition.name) if active_name == condition.name: active_nbr = count self.postcon_selection.set_active(active_nbr) self.on_postcon_changed(self.postcon_selection) return def on_postcon_changed(self, widget): # get the name out of the widget postcon_name = widget.get_active_text() # update the model self.model.postcon_name = postcon_name # Set the Postcon Description section_dict = db_interaction.get_pre_post_con(None) for condition in section_dict: if condition.name == postcon_name: self.model.postcon_descr = condition.description self.model.postcon_code = condition.condition # update the data model viewer self.app.update_model_viewer() def precon_edit_clicked(self, widget): dialog = Edit_Pre_Post_Con_Dialog(self, 'pre', self.precon_selection.get_active()) response = dialog.run() if response == Gtk.ResponseType.OK: self.precon_selection.remove_all() self.set_precon_model(dialog.selection.get_active_text()) dialog.destroy() elif response == Gtk.ResponseType.CANCEL: dialog.destroy() def postcon_edit_clicked(self, widget): dialog = Edit_Pre_Post_Con_Dialog(self, 'post', self.postcon_selection.get_active()) response = dialog.run() if response == Gtk.ResponseType.OK: self.postcon_selection.remove_all() self.set_postcon_model(dialog.selection.get_active_text()) dialog.destroy() elif response == Gtk.ResponseType.CANCEL: dialog.destroy() def on_test_name_change(self, widget): """ if the name of test specification is changed update the model and the model viewer :param: Gtk.Widget widget: widget """ # get the name out of the widget name = widget.get_text() # update the model self.model.name = name # update the data model viewer self.app.update_model_viewer() def on_test_desc_change(self, widget): # get the text out of the buffer content = widget.get_text() # set the test description in the model self.model.description = content # update the data model viewer self.app.update_model_viewer() def on_test_spec_version_change(self, widget): # get the Specification Version out of the text buffer of the widget spec_version = widget.get_text() # update the model self.model.spec_version = spec_version # update the data model viewer self.app.update_model_viewer() def on_test_iasw_version_change(self, widget): # get the IASW Version out of the text buffer of the widget iasw_version = widget.get_text() # update the model self.model.iasw_version = iasw_version # update the data model viewer self.app.update_model_viewer() def on_test_locked_toggled(self, *args): # toggle the value in the widget self.test_is_locked = not self.test_is_locked # write it back to the data model self.model.primary_counter_locked = self.test_is_locked # update the data model viewer self.app.update_model_viewer() def on_comment_change(self, widget): """ if the name of test specification is changed update the model and the model viewer :param: Gtk.Widget widget: widget """ # get the name out of the widget comment = widget.get_text(widget.get_start_iter(), widget.get_end_iter(), True) # update the model self.model.comment = comment # update the data model viewer self.app.update_model_viewer() def on_custom_import_change(self, widget): """ update model if buffer changes :param widget: :return: """ custom_code = widget.get_text(widget.get_start_iter(), widget.get_end_iter(), True) self.model.custom_imports = custom_code self.app.update_model_viewer() def destroy_all_step_widgets(self): """ Destroys all StepWidgets of the current grid """ steps = self.grid.get_children() for child in steps: child.destroy() def build_all_steps(self): """ Creates for every step in the instance of TestSequence a StepWidget-Widget and adds it to the grid. """ def get_top_attach_seq_start(grid, seq_num): y = None for child in grid.get_children(): if isinstance(child, StepWidget): if hasattr(child, 'start_sequence'): if child.start_sequence == seq_num: y = grid.child_get_property(child, 'top-attach') return y def count_seq_children(grid, seq_num): count = 0 for child in grid.get_children(): x = grid.child_get_property(child, 'left-attach') if x == seq_num: count += 1 return count for seq in self.model.sequences: assert isinstance(seq, data_model.TestSequence) seq_num = seq.sequence for step in seq.steps: number = step.step_number step_to_add = StepWidget(model=self.model, seq_num=seq_num, step_number=number, app=self.app, board=self, logger=self.logger) inter_step = InterStepWidget(model=self.model, seq_num=seq_num, app=self.app, board=self, logger=self.logger) # the top-attach value is calculated from: # position where the sequence starts + already placed steps of this sequence nchilds = count_seq_children(grid=self.grid, seq_num=seq_num) if seq_num == 0: # the first step starts at the top-attach position 0, because there is no start sequence step top_attach = nchilds else: # get the top-attach value of the start sequence step which starts this sequence top_attach_start_seq = get_top_attach_seq_start(grid=self.grid, seq_num=seq_num) top_attach = top_attach_start_seq + nchilds self.grid.attach(step_to_add, seq_num, top_attach, 1, 1) self.grid.attach(inter_step, seq_num, top_attach+1, 1, 1) def get_grid_columns(grid): cols = 0 for child in grid.get_children(): x = grid.child_get_property(child, 'left-attach') width = grid.child_get_property(child, 'width') cols = max(cols, x + width) return cols n_col = get_grid_columns(self.grid) return def get_step_widget(self, seq_num, step_number): step_widget = None for child in self.grid.get_children(): if isinstance(child, StepWidget): if child.sequence == seq_num: primary, secondary = data_model.parse_step_number(step_number) step_num = data_model.create_step_number(primary, secondary) if child.step_number == step_num: step_widget = child return step_widget class StepWidget(Gtk.EventBox): """ StepWidget is a composite widget to represent one step of a test specification. """ def __init__(self, model, step_number, seq_num, app, board, logger): super().__init__() assert isinstance(model, data_model.TestSpecification) self.model = model self.sequence = seq_num self.app = app self.board = board self.logger = logger self._step_number = None self._step_description = None self._short_description = None # self._command_code = None # self._verification_code = None self._step_detail_visible = None self._start_sequence = None self._stop_sequence = None self.frame = Gtk.Frame() self.add(self.frame) self.vbox = Gtk.Box() self.vbox.set_orientation(Gtk.Orientation.VERTICAL) self.frame.add(self.vbox) # toolbar self.tbox = Gtk.Box() self.tbox.set_orientation(Gtk.Orientation.HORIZONTAL) self.btn_toggle_detail = Gtk.ToolButton() self.btn_toggle_detail.set_tooltip_text(_('Show step details')) self.btn_toggle_detail.connect('clicked', self.on_toggle_detail) self.label_event_box = Gtk.EventBox() self.label_event_box.set_visible_window(False) self.label_event_box.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) self.label_event_box.connect('button-release-event', self.on_toggle_detail) self.label_step_number = Gtk.Label() self.label_event_box.add(self.label_step_number) self.step_number = step_number self.is_active = Gtk.CheckButton() self.is_active.set_active(True) self.is_active.set_tooltip_text(_('Set if the step is active')) self.is_active.connect('clicked', self.on_toggled_is_active) self.btn_execute_step = Gtk.ToolButton() self.btn_execute_step.set_icon_name('media-playback-start-symbolic') self.btn_execute_step.set_tooltip_text(_('Execute step')) self.btn_execute_step.connect('clicked', self.on_execute_step) self.text_label_event_box = Gtk.EventBox() self.text_label_event_box.set_visible_window(False) self.text_label_event_box.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) self.text_label_event_box.connect('button-release-event', self.on_toggle_detail) self.text_label = Gtk.Label() self.text_label.set_alignment(0, 0.5) self.text_label_event_box.add(self.text_label) self.btn_delete_step = Gtk.ToolButton() self.btn_delete_step.set_icon_name('edit-delete-symbolic') self.btn_delete_step.set_tooltip_text(_('Delete this step')) self.btn_delete_step.connect('clicked', self.on_delete_step) self.tbox.pack_start(self.btn_toggle_detail, False, False, 0) self.tbox.pack_start(self.label_event_box, False, False, 0) self.tbox.pack_start(self.text_label_event_box, True, True, 0) self.tbox.pack_end(self.btn_delete_step, False, False, 0) self.tbox.pack_end(self.is_active, False, False, 0) self.tbox.pack_end(self.btn_execute_step, False, False, 0) self.vbox.pack_start(self.tbox, True, True, 0) # detail area self.detail_box = Gtk.Box() self.detail_box.set_orientation(Gtk.Orientation.VERTICAL) self.detail_box.connect('show', self.on_detail_box_show) # self.detail_box.set_homogeneous(True) Gtk.StyleContext.add_class(self.detail_box.get_style_context(), 'step-detail-box') # for CSS styling self.vbox.pack_start(self.detail_box, True, True, 0) self.set_hexpand(True) # area for the commands self.whole_description_box = Gtk.Grid() self.whole_description_box.set_column_homogeneous(True) # self.whole_commands_box.set_orientation(Gtk.Orientation.HORIZONTAL) # field for the description self.lbl_box_desc = Gtk.Box() self.lbl_box_desc.set_orientation(Gtk.Orientation.HORIZONTAL) self.desc_label = Gtk.Label.new() self.desc_label.set_text(_('Description')) self.lbl_box_desc.pack_start(self.desc_label, False, True, 0) # self.detail_box.pack_start(self.lbl_box_desc, True, True, 0) self.desc_scrolled_window = Gtk.ScrolledWindow() # self.desc_scrolled_window.set_size_request(50, 100) # self.detail_box.pack_start(self.desc_scrolled_window, False, True, 0) self.desc_text_view = Gtk.TextView.new() Gtk.StyleContext.add_class(self.desc_text_view.get_style_context(), 'text-view') self.desc_text_view.set_wrap_mode(Gtk.WrapMode.WORD) # self.desc_text_view.set_accepts_tab(False) self.desc_scrolled_window.add(self.desc_text_view) self.desc_text_buffer = self.desc_text_view.get_buffer() # Step Comment Area # Make the label, inside a own Box to show it on the left end self.lbl_box_step_comment = Gtk.Box() self.lbl_box_step_comment.set_orientation(Gtk.Orientation.HORIZONTAL) self.step_label_comment = Gtk.Label.new() self.step_label_comment.set_text(_('Step Comment')) self.lbl_box_step_comment.pack_start(self.step_label_comment, False, False, 0) # Make the area where the real command is entered self.step_comment_scrolled_window = Gtk.ScrolledWindow() # self.step_comment_scrolled_window.set_size_request(200, 100) self.step_comment_view = GtkSource.View() Gtk.StyleContext.add_class(self.step_comment_view.get_style_context(), 'text-view') self.step_comment_view.set_wrap_mode(Gtk.WrapMode.WORD) self.step_comment_view.set_show_line_numbers(False) self.step_comment_scrolled_window.add(self.step_comment_view) self.step_comment_buffer = self.step_comment_view.get_buffer() # ADD everything to the whole grid self.whole_description_box.set_column_spacing(10) self.whole_description_box.attach(self.lbl_box_desc, 0, 0, 3, 1) self.whole_description_box.attach(self.desc_scrolled_window, 0, 1, 3, 5) self.whole_description_box.attach_next_to(self.lbl_box_step_comment, self.lbl_box_desc, Gtk.PositionType.RIGHT, 3, 1) self.whole_description_box.attach_next_to(self.step_comment_scrolled_window, self.desc_scrolled_window, Gtk.PositionType.RIGHT, 3, 5) self.detail_box.pack_start(self.whole_description_box, True, True, 0) # fields for commands and verification # lm = GtkSource.LanguageManager() # Area for the commands self.whole_commands_box = Gtk.Box() self.whole_commands_box.set_orientation(Gtk.Orientation.VERTICAL) # Make the label, inside a own Box to show it on the left side self.lbl_box_commands = Gtk.Box() self.lbl_box_commands.set_orientation(Gtk.Orientation.HORIZONTAL) self.commands_label = Gtk.Label.new() self.commands_label.set_text(_('Commands')) self.lbl_box_commands.pack_start(self.commands_label, False, False, 0) # self.btn_exec_commands = Gtk.Button.new_from_icon_name(icon_name='media-playback-start', size=Gtk.IconSize.BUTTON) # self.btn_exec_commands.connect('clicked', self.on_exec_commands) # self.lbl_box_commands.pack_start(self.btn_exec_commands, False, False, 0) # Make the area where the real command is entered # self.detail_box.pack_start(self.lbl_box_commands, True, True, 0) self.commands_scrolled_window = Gtk.ScrolledWindow() self.commands_scrolled_window.set_size_request(-1, 200) self.commands_view = GtkSource.View() self.commands_view.set_auto_indent(True) self.commands_view.set_wrap_mode(Gtk.WrapMode.WORD) self.commands_view.set_show_line_numbers(True) # self.commands_view.set_show_right_margin(True) self.commands_view.set_monospace(True) self.commands_view.set_highlight_current_line(True) self.commands_view.set_indent_on_tab(True) self.commands_view.set_insert_spaces_instead_of_tabs(True) self.commands_view.set_indent_width(4) self.commands_view.set_auto_indent(True) self.commands_buffer = self.commands_view.get_buffer() # draganddrop here """ self.commands_view.drag_dest_set(Gtk.DestDefaults.ALL, [], Gdk.DragAction.COPY) self.commands_view.drag_dest_set_target_list(None) self.commands_view.drag_dest_add_text_targets() self.commands_view.connect("drag-motion", self.on_drag_motion_2) self.commands_view.connect("drag-leave", self.on_drag_leave) """ self.commands_buffer.set_language(lngg) # self.commands_buffer.set_style_scheme(self.board.current_scheme) self.commands_scrolled_window.add(self.commands_view) self.whole_commands_box.pack_start(self.lbl_box_commands, False, False, 0) self.whole_commands_box.pack_start(self.commands_scrolled_window, True, True, 0) self.detail_box.pack_start(self.whole_commands_box, True, True, 0) # Area for TM/TC self.whole_tmtc_box = Gtk.Box() self.whole_tmtc_box.set_orientation(Gtk.Orientation.VERTICAL) # box for TM/TC self.tmtc_box = Gtk.Box() self.tmtc_box.set_orientation(Gtk.Orientation.HORIZONTAL) self.tmtc_label = Gtk.Label.new() self.tmtc_label.set_text(_('TM/TC')) self.tmtc_box.pack_start(self.tmtc_label, False, False, 0) self.tmtc_scrolled_window = Gtk.ScrolledWindow() self.tmtc_scrolled_window.set_size_request(-1, 10) self.tmtc_view = GtkSource.View() self.tmtc_view.set_auto_indent(True) self.tmtc_view.set_wrap_mode(Gtk.WrapMode.WORD) self.tmtc_view.set_show_line_numbers(True) # self.tmtc_view.set_show_right_margin(True) self.tmtc_view.set_monospace(True) self.tmtc_view.set_highlight_current_line(True) self.tmtc_view.set_indent_on_tab(True) self.tmtc_view.set_insert_spaces_instead_of_tabs(True) self.tmtc_view.set_indent_width(4) self.tmtc_view.set_auto_indent(True) self.tmtc_buffer = self.tmtc_view.get_buffer() # draganddrop here """ self.tmtc_view.drag_dest_set(Gtk.DestDefaults.ALL, [], Gdk.DragAction.COPY) self.tmtc_view.drag_dest_set_target_list(None) self.tmtc_view.drag_dest_add_text_targets() self.tmtc_view.connect("drag-motion", self.on_drag_motion_2) self.tmtc_view.connect("drag-leave", self.on_drag_leave) """ self.tmtc_buffer.set_language(lngg) # self.tmtc_buffer.set_style_scheme(self.board.current_scheme) self.tmtc_scrolled_window.add(self.tmtc_view) self.whole_tmtc_box.pack_start(self.tmtc_box, False, False, 0) self.whole_tmtc_box.pack_start(self.tmtc_scrolled_window, True, True, 0) self.detail_box.pack_start(self.whole_tmtc_box, True, True, 0) # area for the verification self.whole_verification_box = Gtk.Grid() self.whole_verification_box.set_column_homogeneous(True) # Left side of the verification area, where the verification-commands a entered # Make the label, inside an own Box to show it on the left side self.lbl_box_verification = Gtk.Box() self.lbl_box_verification.set_orientation(Gtk.Orientation.HORIZONTAL) self.verification_label = Gtk.Label.new() self.verification_label.set_text(_('Verification Code')) self.verification_label.set_tooltip_text(_('The verification code-block must define a variable "result" that is TRUE/FALSE')) # self.btn_exec_verification = Gtk.Button.new_from_icon_name(icon_name='media-playback-start', size=Gtk.IconSize.BUTTON) # self.btn_exec_verification.connect('clicked', self.on_exec_verification) self.lbl_box_verification.pack_start(self.verification_label, False, False, 0) # self.lbl_box_verification.pack_start(self.btn_exec_verification, False, False, 0) # self.detail_box.pack_start(self.lbl_box_verification, True, True, 0) self.verification_scrolled_window = Gtk.ScrolledWindow() self.verification_scrolled_window.set_size_request(-1, 125) self.verification_view = GtkSource.View() self.verification_view.set_auto_indent(True) self.verification_view.set_show_line_numbers(True) self.verification_view.set_wrap_mode(Gtk.WrapMode.WORD) # self.verification_view.set_show_right_margin(True) self.verification_view.set_monospace(True) self.verification_view.set_highlight_current_line(True) self.verification_view.set_indent_on_tab(True) self.verification_view.set_insert_spaces_instead_of_tabs(True) self.verification_view.set_indent_width(4) self.verification_view.set_auto_indent(True) self.verification_buffer = self.verification_view.get_buffer() self.verification_buffer.set_language(lngg) # self.verification_buffer.set_style_scheme(self.board.current_scheme) self.verification_scrolled_window.add(self.verification_view) # Right side of the verification area, the comment box # Make the label, inside a own Box to show it on the left end self.lbl_box_verification_description = Gtk.Box() self.lbl_box_verification_description.set_orientation(Gtk.Orientation.HORIZONTAL) self.verification_label_description = Gtk.Label.new() self.verification_label_description.set_text(_('Verification Description')) self.lbl_box_verification_description.pack_start(self.verification_label_description, False, False, 0) # Make the area where the real command is entered self.verification_description_scrolled_window = Gtk.ScrolledWindow() #self.verification_comment_scrolled_window.set_size_request(200, 100) self.verification_description_view = GtkSource.View() Gtk.StyleContext.add_class(self.verification_description_view.get_style_context(), 'text-view') self.verification_description_view.set_show_line_numbers(False) self.verification_description_view.set_wrap_mode(Gtk.WrapMode.WORD) self.verification_description_scrolled_window.add(self.verification_description_view) self.verification_description_buffer = self.verification_description_view.get_buffer() #ADD everything to the whole grid self.whole_verification_box.set_column_spacing(10) self.whole_verification_box.attach(self.lbl_box_verification, 0, 0, 3, 1) self.whole_verification_box.attach(self.verification_scrolled_window, 0, 1, 3, 5) self.whole_verification_box.attach_next_to(self.lbl_box_verification_description, self.lbl_box_verification, Gtk.PositionType.RIGHT, 3, 1) self.whole_verification_box.attach_next_to(self.verification_description_scrolled_window, self.verification_scrolled_window, Gtk.PositionType.RIGHT, 3, 5) self.detail_box.pack_start(self.whole_verification_box, True, True, 0) # fill the step with data before connecting the signals (!) self.set_data_in_widget() # default behavior: step details are hidden self.step_detail_visible = False # drag and drop: the StepWidget as a drag source self.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, [], Gdk.DragAction.COPY) self.drag_source_set_target_list(None) # drag and drop: the StepWidget as a drag destination self.drag_dest_set(Gtk.DestDefaults.ALL, [], Gdk.DragAction.COPY) self.drag_dest_set_target_list(None) self.drag_dest_add_text_targets() self.drag_source_add_text_targets() self.connect('drag-data-received', self.on_drag_data_received) self.connect('drag-motion', self.on_drag_motion) self.connect('drag-leave', self.on_drag_leave) self.connect('drag-begin', self.on_drag_begin) self.connect('drag-data-get', self.on_drag_data_get) self.connect('drag-drop', self.on_drag_drop) # right click menu (context menu) self.menu = StepRightClickMenu(step_widget=self) self.connect('button-press-event', self.on_button_press) # connect the signals self.desc_text_buffer.connect('changed', self.on_description_buffer_changed) self.commands_buffer.connect('changed', self.on_commands_buffer_changed) self.step_comment_buffer.connect('changed', self.on_step_comment_buffer_changed) self.verification_buffer.connect('changed', self.on_verification_buffer_changed) self.verification_description_buffer.connect('changed', self.on_verification_description_buffer_changed) Gtk.StyleContext.add_class(self.get_style_context(), 'step-widget') @property def step_detail_visible(self): return self._step_detail_visible @step_detail_visible.setter def step_detail_visible(self, value: bool): assert isinstance(value, bool) self._step_detail_visible = value # set the visibility status of the widget self.detail_box.set_visible(self.step_detail_visible) if self.step_detail_visible is True: self.btn_toggle_detail.set_icon_name('pan-down-symbolic') else: self.btn_toggle_detail.set_icon_name('pan-end-symbolic') @property def step_number(self): return self._step_number @step_number.setter def step_number(self, value: str): """ If the attribute step_description is set, other actions are triggered.""" assert isinstance(value, (str, int, float)) stp_nmbr_pri, stp_nmbr_sec = data_model.parse_step_number(value) stp_nmbr = data_model.create_step_number(stp_nmbr_pri, stp_nmbr_sec) self._step_number = stp_nmbr # set the text in the step number label self.label_step_number.set_text('{} {}: '.format(_('Step'), str(self.step_number))) @property def start_sequence(self): return self._start_sequence @start_sequence.setter def start_sequence(self, value: int): assert isinstance(value, int) or value is None self._start_sequence = value @property def stop_sequence(self): return self._stop_sequence @stop_sequence.setter def stop_sequence(self, value: int): assert isinstance(value, int) or value is None self._stop_sequence = value @property def step_description(self): return self._step_description @step_description.setter def step_description(self, value: str): """ If the attribute step_description is set, other actions are triggered.""" assert isinstance(value, str) self._step_description = value # Setting the description for a step in the data model # find the correct step within the data model stp_ndx = self.model.get_sequence(self.sequence).get_step_index(self.step_number) step_in_data_model = self.model.get_sequence(self.sequence).steps[stp_ndx] # use the setter of the data model if isinstance(step_in_data_model, data_model.Step): step_in_data_model.description = self.step_description else: self.logger('step with the step number {} could not be found'.format(self.step_number)) # set the short description attribute self.short_description = self.step_description @property def short_description(self): return self._short_description @short_description.setter def short_description(self, value: str): """ Creates the short description of the step out of the description. Sets the text in the StepWidget toolbar label """ assert isinstance(value, str) len_short_desc = 42 if len(value) > len_short_desc: short_d = value[0:len_short_desc] + '...' else: short_d = value self._short_description = short_d # update the text of the label self.text_label.set_text(self.short_description) self.text_label.set_tooltip_text(self.step_description) def set_data_in_widget(self): self.set_is_active_in_widget() self.set_description_in_widget() self.set_commands_in_widget() self.set_step_comment_in_widget() self.set_verification_in_widget() self.set_verification_description_in_widget() self.set_start_sequence_in_widget() self.set_stop_sequence_in_widget() return def set_is_active_in_widget(self): """ Toggles the checkbox""" # Change the active state of a step in the data model stp_ndx = self.model.get_sequence(self.sequence).get_step_index(self.step_number) state = self.model.get_sequence(self.sequence).steps[stp_ndx].is_active self.is_active.set_active(state) return def set_description_in_widget(self): """ copy the description of the step from the model to the text view buffer and make a short description for the StepWidget toolbar """ stp_ndx = self.model.get_sequence(self.sequence).get_step_index(self.step_number) desc = self.model.get_sequence(self.sequence).steps[stp_ndx].description self.desc_text_buffer.set_text(desc) self.step_description = desc return def set_commands_in_widget(self): """ gets the commands from the model and sets it in the commands buffer in order to display it """ stp_ndx = self.model.get_sequence(self.sequence).get_step_index(self.step_number) commands = self.model.get_sequence(self.sequence).steps[stp_ndx].command_code self.commands_buffer.set_text(commands) return def set_step_comment_in_widget(self): """ gets the commands comment from the model and sets it in the commands comment buffer in order to display it """ stp_ndx = self.model.get_sequence(self.sequence).get_step_index(self.step_number) step_comment = self.model.get_sequence(self.sequence).steps[stp_ndx].step_comment self.step_comment_buffer.set_text(step_comment) return def set_verification_in_widget(self): """ gets the commands from the model and sets it in the commands buffer in order to display it """ stp_ndx = self.model.get_sequence(self.sequence).get_step_index(self.step_number) verification = self.model.get_sequence(self.sequence).steps[stp_ndx].verification_code self.verification_buffer.set_text(verification) return def set_verification_description_in_widget(self): """ gets the commands comment from the model and sets it in the commands comment buffer in order to display it """ stp_ndx = self.model.get_sequence(self.sequence).get_step_index(self.step_number) verification_description = self.model.get_sequence(self.sequence).steps[stp_ndx].verification_description self.verification_description_buffer.set_text(verification_description) return def set_start_sequence_in_widget(self): stp_ndx = self.model.get_sequence(self.sequence).get_step_index(self.step_number) step = self.model.get_sequence(self.sequence).steps[stp_ndx] if hasattr(step, 'start_sequence'): self.start_sequence = step.start_sequence return def set_stop_sequence_in_widget(self): stp_ndx = self.model.get_sequence(self.sequence).get_step_index(self.step_number) step = self.model.get_sequence(self.sequence).steps[stp_ndx] if hasattr(step, 'stop_sequence'): self.stop_sequence = step.stop_sequence return @staticmethod def read_out_text_buffer(buffer: Gtk.TextBuffer): """ Reads out the text buffer :rtype: str """ assert isinstance(buffer, Gtk.TextBuffer) content = buffer.get_text(start=buffer.get_start_iter(), end=buffer.get_end_iter(), include_hidden_chars=True) return content def get_commands_from_widget(self): """ Reads out the source buffer of the commands :return: The code of the commands of the step :rtype: str """ commands_buffer = self.commands_buffer content = commands_buffer.get_text(start=commands_buffer.get_start_iter(), end=commands_buffer.get_end_iter(), include_hidden_chars=True) return content def get_verification_from_widget(self): """ Reads out the source buffer for the verification :return: The code of the verification of the step :rtype: str """ verification_buffer = self.verification_buffer content = verification_buffer.get_text(start=verification_buffer.get_start_iter(), end=verification_buffer.get_end_iter(), include_hidden_chars=True) return content def get_active_state_from_widget(self): """ Get the current status of the checkbox if a step is active. :return: state of the toggle button :rtype: bool """ return self.is_active.get_active() def on_drag_begin(self, widget, drag_context): pass def on_drag_data_get(self, widget, drag_context, data, info, time): """ Collect data about the step that is dragged. Set it as the selection-data in order to transfer it to the receiving widget. """ step_index = self.model.get_sequence(self.sequence).get_step_index(self.step_number) step = self.model.get_sequence(self.sequence).steps[step_index] assert isinstance(step, data_model.Step) step_number = step.step_number description = step.description comment = step.step_comment command_code = step.command_code verification_code = step.verification_code verification_descr = step.verification_description data_type = dnd_data_parser.data_type_step if (verification_descr or verification_code) else dnd_data_parser.data_type_snippet data_string = dnd_data_parser.create_datastring(data_type, self.sequence, step_number, description, comment, command_code, verification_code, verification_descr, logger=self.logger) # set the text in the selection data object data.set_text(data_string, -1) def on_drag_motion(self, widget, drag_context, x, y, time): # get the widget position in the grid, the widget below and above grid = self.get_parent() left = grid.child_get_property(self, 'left-attach') top = grid.child_get_property(self, 'top-attach') widget_above = grid.get_child_at(left, top - 1) widget_below = grid.get_child_at(left, top + 1) # decide if the cursor position is in he upper or lower quarter of the widget widget_height = widget.get_allocated_height() if y < widget_height * widget_grip_upper: # upper quarter # highlight the widget above if widget_above is not None: Gtk.StyleContext.add_class(widget_above.get_style_context(), 'highlight') if widget_below is not None: Gtk.StyleContext.remove_class(widget_below.get_style_context(), 'highlight') Gtk.StyleContext.remove_class(widget.get_style_context(), 'highlight') elif y > widget_height * widget_grip_lower: # lower quarter # highlight the widget below if widget_below is not None: Gtk.StyleContext.add_class(widget_below.get_style_context(), 'highlight') if widget_above is not None: Gtk.StyleContext.remove_class(widget_above.get_style_context(), 'highlight') Gtk.StyleContext.remove_class(widget.get_style_context(), 'highlight') else: # highlight the widget itself Gtk.StyleContext.add_class(widget.get_style_context(), 'highlight') if widget_above is not None: Gtk.StyleContext.remove_class(widget_above.get_style_context(), 'highlight') if widget_below is not None: Gtk.StyleContext.remove_class(widget_below.get_style_context(), 'highlight') widget.show_all() def on_drag_leave(self, widget, drag_context, time): # removing the highlighting which was done in on_drag_motion Gtk.StyleContext.remove_class(widget.get_style_context(), 'highlight') widget.show_all() def on_drag_drop(self, widget, drag_context, x, y, timestamp, *args): pass def on_drag_data_received(self, widget, drag_context, x, y, selection_data, info, time): """ Depending on the source of the drag operation, the widget shows different behavior. If the drag source was another step widget the order of the steps should be changed, thus an indicator for inserting above/below is shown. If the drag source was and entry in the codereuse-feature, the step should be highlighted, indicating to insert a description and code. Also the drop position will influence what happens. """ # decide if the cursor position is in he upper or lower quarter of the widget widget_height = widget.get_allocated_height() # parse the received data data_string = selection_data.get_text() data = dnd_data_parser.read_datastring(data_string, logger=self.logger) drag_source_type = data['data_type'] #print(drag_source_type) if drag_source_type == dnd_data_parser.data_type_snippet: # data gets copied if y < widget_height * widget_grip_upper: # upper quarter # add a step above step = self.model.get_sequence(self.sequence).add_step_above(reference_step_position=self.step_number) elif y > widget_height * widget_grip_lower: # lower quarter # add a step below step = self.model.get_sequence(self.sequence).add_step_below(reference_step_position=self.step_number) else: step = self.model.get_sequence(self.sequence).get_step(self.step_number) # set the data into the test script data model step.description = data['description'] step.command_code = data['command_code'] step.step_comment = data['comment'] step.verification_code = data['verification_code'] step.verification_description = data['verification_descr'] if drag_source_type == dnd_data_parser.data_type_step: # a step is moved step_number = data['step_number'] dragged_step_idx = self.model.get_sequence(self.sequence).get_step_index(step_number) dropped_on_step_idx = self.model.get_sequence(self.sequence).get_step_index(self.step_number) widget_height = self.get_allocated_height() if y < widget_height * widget_grip_upper: if dragged_step_idx < dropped_on_step_idx: self.model.get_sequence(self.sequence).move_step(dragged_step_idx, dropped_on_step_idx-1) elif dragged_step_idx > dropped_on_step_idx: self.model.get_sequence(self.sequence).move_step(dragged_step_idx, dropped_on_step_idx) elif y > widget_height * widget_grip_lower: if dragged_step_idx < dropped_on_step_idx: self.model.get_sequence(self.sequence).move_step(dragged_step_idx, dropped_on_step_idx) elif dragged_step_idx > dropped_on_step_idx: self.model.get_sequence(self.sequence).move_step(dragged_step_idx, dropped_on_step_idx+1) # update the board self.board.update_widget_data() # update the model view self.app.update_model_viewer() def on_delete_step(self, button): """ Deletes the step from the data model (TestSequence) :param button: the button widget """ # deletes the step from the data model (TestSequence) self.model.get_sequence(self.sequence).remove_step(self.step_number) # update the board self.board.update_widget_data() # update the model view self.app.update_model_viewer() def on_toggled_is_active(self, toggled_button): """ Signal if the checkbox of a steps active status was toggled. """ current_state = self.get_active_state_from_widget() stp_ndx = self.model.get_sequence(self.sequence).get_step_index(self.step_number) self.model.get_sequence(self.sequence).steps[stp_ndx].is_active = current_state self.app.update_model_viewer() def on_description_buffer_changed(self, text_buffer): """ Signal 'changed' for the description text buffer. For example the user typed. """ # get the description out of the text buffer of the widget # description = self.get_description_from_widget() self.step_description = self.read_out_text_buffer(text_buffer) # Setting the description string for a step in the data model # find the correct step within the data model stp_ndx = self.model.get_sequence(self.sequence).get_step_index(self.step_number) step_in_data_model = self.model.get_sequence(self.sequence).steps[stp_ndx] # use the setter of the data model if isinstance(step_in_data_model, data_model.Step): step_in_data_model.description = self.step_description else: self.logger('step with the step number {} could not be found'.format(self.step_number)) # update the model # ToDo # update the data model viewer self.app.update_model_viewer() # update short desc # self.update_short_description() def on_commands_buffer_changed(self, text_buffer): """ Signal 'changed' for the commands source buffer """ # get the code of the commands out of the buffer of the widget commands = self.get_commands_from_widget() # Setting the commands string for a step in the data model # find the correct step within the data model stp_ndx = self.model.get_sequence(self.sequence).get_step_index(self.step_number) step_in_data_model = self.model.get_sequence(self.sequence).steps[stp_ndx] # use the setter of the data model if isinstance(step_in_data_model, data_model.Step): step_in_data_model.command_code = commands else: self.logger('step with the step number {} could not be found'.format(self.step_number)) # update the data model viewer self.app.update_model_viewer() def on_step_comment_buffer_changed(self, text_buffer): """ Signal 'changed' for the commands comment buffer """ # get the text of the commands comment out of the buffer of the widget step_comment = self.read_out_text_buffer(text_buffer) # Setting the commands string for a step in the data model # find the correct step within the data model stp_ndx = self.model.get_sequence(self.sequence).get_step_index(self.step_number) step_in_data_model = self.model.get_sequence(self.sequence).steps[stp_ndx] # use the setter of the data model if isinstance(step_in_data_model, data_model.Step): step_in_data_model.step_comment = step_comment else: self.logger('step with the step number {} could not be found'.format(self.step_number)) # update the data model viewer self.app.update_model_viewer() def on_verification_buffer_changed(self, text_buffer): """ Signal 'changed' for the verification source buffer """ # get the code of the verification out of the buffer of the widget verification = self.get_verification_from_widget() # Setting the verification string for a step in the data model # find the correct step within the data model stp_ndx = self.model.get_sequence(self.sequence).get_step_index(self.step_number) step_in_data_model = self.model.get_sequence(self.sequence).steps[stp_ndx] # use the setter of the data model if isinstance(step_in_data_model, data_model.Step): step_in_data_model.verification_code = verification else: self.logger('step with the step number {} could not be found'.format(self.step_number)) # update the data model viewer self.app.update_model_viewer() def on_verification_description_buffer_changed(self, text_buffer): """ Signal 'changed' for the verification description buffer """ # get the code of the verification out of the buffer of the widget verification_description = self.read_out_text_buffer(text_buffer) # Setting the verification string for a step in the data model # find the correct step within the data model stp_ndx = self.model.get_sequence(self.sequence).get_step_index(self.step_number) step_in_data_model = self.model.get_sequence(self.sequence).steps[stp_ndx] # use the setter of the data model if isinstance(step_in_data_model, data_model.Step): step_in_data_model.verification_description = verification_description else: self.logger('step with the step number {} could not be found'.format(self.step_number)) # update the data model viewer self.app.update_model_viewer() def on_exec_commands(self, button): # get the code of the commands out of the buffer of the widget commands = str(self.get_commands_from_widget()) #Check if CCS is open if not cfl.is_open('editor'): print('CCS-Editor has to be started first') logger.info('CCS-Editor has to be running if a step should be executed') return # Connect to the editor and send the commands to the terminal via D-Bus ed = cfl.dbus_connection('editor') cfl.Functions(ed, '_to_console_via_socket', commands) #import editor #x = editor.CcsEditor() #x._to_console_via_socket(commands) def on_exec_verification(self, button): # get the code of the commands out of the buffer of the widget verification = self.get_verification_from_widget() #ack = misc.to_console_via_socket(verification) #print(ack) def on_execute_step(self, *args): if not cfl.is_open('editor'): print('CCS-Editor has to be started first') logger.info('CCS-Editor has to be running if a step should be executed') return commands = str(self.get_commands_from_widget()) if len(commands) == 0: return ed = cfl.dbus_connection('editor') cfl.Functions(ed, '_to_console_via_socket', commands) def on_toggle_detail(self, toolbutton, *args): """ The button to show/hide the step details was clicked. The visible status of the detail area is inverted and the button icon is changed. :param Gtk.ToolButton toolbutton: the button widget which was clicked """ self.step_detail_visible = not self.detail_box.is_visible() # if showing the detail view, set the cursor into the description field if self.step_detail_visible is True: self.do_grab_focus(self.desc_text_view) return False def on_detail_box_show(self, *args): self.step_detail_visible = self.step_detail_visible def on_button_press(self, widget, event, *args): if event.button == 3: # right mouse button clicked # show the right-click context menu widget.menu.popup_at_pointer() class InterStepWidget(Gtk.Box): """ This widget is used to be put between two steps. It is used to highlight if a step is dragged over it and draw an arrow between two steps. """ def __init__(self, model, seq_num, app, board, logger=logger): super().__init__() Gtk.StyleContext.add_class(self.get_style_context(), 'inter-step-widget') self.model = model self.app = app self.board = board self.logger = logger self.sequence = seq_num # add the drawing area for the arrow self.drawingarea = Gtk.DrawingArea() self.drawingarea_height = 16 self.hovered_over = False self.drawingarea.set_size_request(10, self.drawingarea_height) self.pack_start(self.drawingarea, True, True, 0) self.drawingarea.connect('draw', self.draw) # self.drawingarea.connect('realize', self.realize) # self.drawingarea.connect('size-allocate', self.size_allocate) # drag and drop: make this widget a drag destination self.drag_dest_set(Gtk.DestDefaults.ALL, [], Gdk.DragAction.COPY) self.drag_dest_set_target_list(None) self.drag_dest_add_text_targets() self.connect('drag-data-received', self.on_drag_data_received) self.connect('drag-motion', self.on_drag_motion) self.connect('drag-leave', self.on_drag_leave) self.connect('drag-drop', self.on_drag_drop) # def draw_arrow(self, ctx, x, y, size): # """ # Uses cairo context functions to draw the arrow. # """ # ctx.save() # ctx.new_path() # # # draw the arrow (construction of the path) # ctx.move_to(x, y) # ctx.rel_line_to(0, 1/3*size) # ctx.rel_line_to(-1/3*size, 0) # ctx.rel_line_to(1/3*size, 2/3*size) # ctx.rel_line_to(1/3*size, -2/3*size) # ctx.rel_line_to(-1/3*size, 0) # ctx.close_path() # # # draw the path and fill it # ctx.stroke_preserve() # ctx.fill() # ctx.restore() # return def draw_arrow_head(self, ctx, x, y, size): """ Uses cairo context functions to draw the arrow. """ ctx.save() ctx.new_path() # draw the arrow (construction of the path) ctx.move_to(x, y+size) ctx.rel_line_to(-1/2*size, -size) ctx.rel_line_to(1*size, 0) ctx.rel_line_to(-1/2*size, size) ctx.close_path() # draw the path and fill it ctx.stroke_preserve() ctx.fill() ctx.restore() return def draw_insert_here_arrows(self, ctx, x, y, size): """ Uses cairo context functions to draw the arrow. """ ctx.save() ctx.new_path() size = size/2 # draw the arrow (construction of the path) ctx.move_to(x, y+size) ctx.rel_line_to(-1/2*size, -size) ctx.rel_line_to(1*size, 0) ctx.rel_line_to(-1/2*size, size) ctx.move_to(0, y+size) ctx.rel_line_to(2*x, 0) ctx.move_to(x, y+size) ctx.rel_line_to(-1/2*size, size) ctx.rel_line_to(1*size, 0) ctx.rel_line_to(-1/2*size, -1*size) ctx.close_path() # draw the path and fill it ctx.stroke_preserve() ctx.fill() ctx.restore() return # def realize(self, *args): # pass # # def size_allocate(self, *args): # """ # Resize the drawing area: figure out the height of a step widget, with no details shown (for the length of the # arrow) and resize the drawing area. # """ # for child in self.board.grid.get_children(): # if isinstance(child, StepWidget): # if not child.step_detail_visible: # # height = child.get_allocated_height() # # width = child.get_allocated_width() # natural_height = child.get_preferred_height()[1] # natural_width = child.get_preferred_width()[1] # width = self.get_allocated_width() # # self.drawingarea.set_size_request(width, natural_height) # self.drawingarea.set_size_request(10, 10) def draw(self, da, ctx, *args): """ Draws an arrow if: * a step is below this interstep-widget (vertical arrows) * another sequence is started (horizontal arrows) The arrows are drawn: starting point is the half width of the widget, because in the grid all are the same width The length of the arrow is determined by the height of a step widget with no details shown. """ width = self.get_allocated_width() # for child in self.board.grid.get_children(): # if isinstance(child, InterStepWidget): # print('{}, {}'.format(self.board.grid.child_get_property(child, 'left-attach'), # self.board.grid.child_get_property(child, 'top-attach'))) # print('*****') # figure out the height of a step widget, with do details shown (for the length of the arrow) for child in self.board.grid.get_children(): if isinstance(child, StepWidget): if not child.step_detail_visible: # height = child.get_allocated_height() height = child.get_preferred_height()[1] # figure out the top-attach of this widget for child in self.board.grid.get_children(): if child is self: self.top_attach = self.board.grid.child_get_property(child, 'top-attach') self.left_attach = self.board.grid.child_get_property(child, 'left-attach') # figure out, if there comes another step after this InterStep widget another_step_follows = False for child in self.board.grid.get_children(): if isinstance(child, StepWidget): child_top_attach = self.board.grid.child_get_property(child, 'top-attach') child_left_attach = self.board.grid.child_get_property(child, 'left-attach') if child_left_attach == self.left_attach: # only widgets in the same column are considered if child_top_attach > self.top_attach: another_step_follows = True # figure out, if there is another step in front this InterStep widget another_step_in_front = False for child in self.board.grid.get_children(): if isinstance(child, StepWidget): child_top_attach = self.board.grid.child_get_property(child, 'top-attach') child_left_attach = self.board.grid.child_get_property(child, 'left-attach') if child_left_attach == self.left_attach: # only widgets in the same column are considered if child_top_attach < self.top_attach: another_step_in_front = True # draw the arrow if another step follows ctx.set_source_rgb(0, 0, 0) ctx.set_line_width(self.drawingarea_height / 10) ctx.set_tolerance(0.1) ctx.set_line_join(cairo.LINE_JOIN_ROUND) if self.hovered_over: self.draw_insert_here_arrows(ctx, width/2, 0, self.drawingarea_height) else: if another_step_follows: self.draw_arrow_head(ctx, width/2, 0, self.drawingarea_height) return def on_drag_motion(self, widget, drag_context, x, y, time): self.hovered_over = True self.drawingarea.queue_draw() Gtk.StyleContext.add_class(widget.get_style_context(), 'highlight-2') widget.show_all() def on_drag_leave(self, widget, drag_context, time): self.hovered_over = False # removing the highlighting which was done in on_drag_motion Gtk.StyleContext.remove_class(widget.get_style_context(), 'highlight-2') widget.show_all() def on_drag_drop(self, widget, drag_context, x, y, timestamp, *args): pass def on_drag_data_received(self, widget, drag_context, x, y, selection_data, info, time): self.hovered_over = False # figure out what was the source of the drag operation data_string = selection_data.get_text() data = dnd_data_parser.read_datastring(data_string, logger=self.logger) drag_source_type = data['data_type'] # get the widget above grid = self.get_parent() left = grid.child_get_property(self, 'left-attach') top = grid.child_get_property(self, 'top-attach') widget_above = grid.get_child_at(left, top - 1) # create a new step and fill it with data if drag_source_type == dnd_data_parser.data_type_snippet: # add a step below the position of the widget above if isinstance(widget_above, StepWidget): dest_step_above_number = widget_above.step_number new_step = self.model.get_sequence(self.sequence).add_step_below(reference_step_position=dest_step_above_number) # set the data into the test script data model new_step.description = data['description'] new_step.step_comment = data['comment'] new_step.command_code = data['command_code'] new_step.verification_code = data['verification_code'] new_step.verification_description = data['verification_descr'] if drag_source_type == dnd_data_parser.data_type_step: # a step is moved source_sequence = int(data['sequence']) source_step_number = data['step_number'] source_step_idx = self.model.get_sequence(source_sequence).get_step_index(source_step_number) if isinstance(widget_above, StepWidget): dest_step_above_number = widget_above.step_number dest_step_above_sequence = widget_above.sequence dest_step_above_idx = self.model.get_sequence(dest_step_above_sequence).get_step_index(dest_step_above_number) if source_sequence == dest_step_above_sequence: # moving the step within the same sequence if source_step_idx < dest_step_above_idx: self.model.get_sequence(dest_step_above_sequence).move_step(source_step_idx, dest_step_above_idx) elif source_step_idx > dest_step_above_idx: self.model.get_sequence(dest_step_above_sequence).move_step(source_step_idx, dest_step_above_idx+1) else: # the step is moved to another sequence: # add the step within the new sequence and remove it from the old sequence seq = self.model.get_sequence(dest_step_above_sequence) new_step = self.model.get_sequence(dest_step_above_sequence).add_step_below(reference_step_position=dest_step_above_number) # set the data into the test script data model new_step.description = data['description'] new_step.step_comment = data['comment'] new_step.command_code = data['command_code'] new_step.verification_code = data['verification_code'] new_step.verification_description = data['verification_descr'] # ToDo # remove it from the old sequence dragged_from_seq = self.model.get_sequence(source_sequence) dragged_from_seq.remove_step(step_number=source_step_number) # update the board self.board.update_widget_data() # update the model view self.app.update_model_viewer() class StepRightClickMenu(Gtk.Menu): def __init__(self, step_widget): super().__init__() self.step_widget = step_widget entry_1 = Gtk.MenuItem('Insert step above') self.attach(entry_1, 0, 1, 0, 1) entry_1.show() entry_1.connect('activate', self.on_insert_step_above, self.step_widget) entry_2 = Gtk.MenuItem('Insert step below') self.attach(entry_2, 0, 1, 1, 2) entry_2.show() entry_2.connect('activate', self.on_insert_step_below, self.step_widget) def on_insert_step_above(self, menu_item, step_widget, *args): step_clicked_on = step_widget.step_number step_widget.model.get_sequence(step_widget.sequence).add_step_above(reference_step_position=step_clicked_on) self.step_widget.board.update_widget_data() def on_insert_step_below(self, menu_item, step_widget, *args): step_clicked_on = step_widget.step_number step_widget.model.get_sequence(step_widget.sequence).add_step_below(reference_step_position=step_clicked_on) self.step_widget.board.update_widget_data() class Edit_Pre_Post_Con_Dialog(Gtk.Dialog): def __init__(self, parent, pre_post, selection): # Gtk.Dialog.__init__(self, title=pre_post.upper() + ' -Conditions') super(Edit_Pre_Post_Con_Dialog, self).__init__(title=pre_post.upper() + '-conditions') self.add_buttons( Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OK, Gtk.ResponseType.OK ) self.set_default_size(400, 400) self.first_entry = selection self.win = parent self.file_path = os.path.join(confignator.get_option('paths', 'tst'), 'tst/generator_templates/co_'+pre_post+'_condition_entry.py') # self.pre_post = pre_post self.pre_post = None self.make_section_dict() self.view() self.show_all() def make_section_dict(self): # if self.pre_post == 'pre': # self.section_dict = db_interaction.get_pre_post_con('pre') # else: # self.section_dict = db_interaction.get_pre_post_con('post') self.section_dict = db_interaction.get_pre_post_con(None) def view(self): # self.main_box = Gtk.Box() # self.main_box.set_orientation(Gtk.Orientation.VERTICAL) self.selection_box = Gtk.Box() self.selection_box.set_orientation(Gtk.Orientation.HORIZONTAL) self.make_description_viewer() self.make_text_viewer() self.selection = Gtk.ComboBoxText.new_with_entry() self.make_con_sections_model() self.selection.connect("changed", self.on_name_combo_changed) self.selection.set_entry_text_column(0) self.selection.set_active(self.first_entry) self.save_button = Gtk.Button.new_with_label('Save') self.save_button.connect("clicked", self.on_save_button) self.delete_button = Gtk.Button.new_with_label('Delete') self.delete_button.connect("clicked", self.on_delete_button) self.selection_box.pack_start(self.selection, False, True, 0) self.selection_box.pack_start(self.save_button, False, True, 0) self.selection_box.pack_start(self.delete_button, False, True, 0) box = self.get_content_area() # Gtk.StyleContext.add_class(box.get_style_context(), 'cond-dialog') # box.set_margin_top(5) # box.set_margin_bottom(5) # box.set_margin_left(5) # box.set_margin_right(5) box.pack_start(self.selection_box, False, True, 3) box.pack_start(self.descr_lbl_box, False, False, 0) box.pack_start(self.descr_scrolled_window, False, False, 0) box.pack_start(self.con_lbl_box, False, False, 0) box.pack_start(self.con_scrolled_window, True, True, 0) # box.add(self.selection_box) # box.pack_end(self.scrolled_window, True, True, 0) def make_con_sections_model(self): for condition in self.section_dict: self.selection.append_text(condition.name) return def on_name_combo_changed(self, widget): name = widget.get_active_text() if name: for condition in self.section_dict: if condition.name == name: self.descr_textview.get_buffer().set_text(condition.description) self.con_buffer.set_text(condition.condition) else: self.descr_textview.get_buffer().set_text('') self.con_buffer.set_text('') return def on_save_button(self, widget): descr_buffer = self.descr_textview.get_buffer() name = self.selection.get_active_text() if not name: return descr = descr_buffer.get_text(descr_buffer.get_start_iter(), descr_buffer.get_end_iter(), True) condition = self.con_buffer.get_text(self.con_buffer.get_start_iter(), self.con_buffer.get_end_iter(), True) db_interaction.write_into_pre_post_con(code_type=self.pre_post, name=name, description=descr, code_block=condition) time.sleep(0.1) # Sleep shorty so the condition can be written to the database # Refresh the combo box entries self.make_section_dict() self.selection.remove_all() self.make_con_sections_model() return def on_delete_button(self, widget): name = self.selection.get_active_text() for con in self.section_dict: if con.name == name: db_interaction.delete_db_row_pre_post(con.id) time.sleep(0.1) # Sleep shorty so the condition can be deleted from the database # Refresh the combo box entries self.make_section_dict() self.selection.remove_all() self.make_con_sections_model() self.selection.set_active(0) return def make_description_viewer(self): # Label in a Box to have it on the left boarder self.descr_lbl_box = Gtk.HBox() descr_lbl = Gtk.Label() descr_lbl.set_text('Description') self.descr_lbl_box.pack_start(descr_lbl, False, False, 0) # a scrollbar for the child widget (that is going to be the textview) self.descr_scrolled_window = Gtk.ScrolledWindow() self.descr_scrolled_window.set_border_width(5) # we scroll only if needed self.descr_scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) # a text buffer (stores text) buffer = Gtk.TextBuffer() # a textview (displays the buffer) self.descr_textview = Gtk.TextView(buffer=buffer) # wrap the text, if needed, breaking lines in between words self.descr_textview.set_wrap_mode(Gtk.WrapMode.WORD) # textview is scrolled self.descr_scrolled_window.add(self.descr_textview) def make_text_viewer(self): # Label in a Box to have it on the left boarder self.con_lbl_box = Gtk.HBox() con_lbl = Gtk.Label() con_lbl.set_text('Condition') self.con_lbl_box.pack_start(con_lbl, False, False, 0) self.con_scrolled_window = Gtk.ScrolledWindow() self.con_scrolled_window.set_tooltip_text('Set variable "success" to True/False to check if conditon is fulfilled') #self.commands_scrolled_window.set_size_request(50, 100) self.con_view = GtkSource.View() self.con_view.set_auto_indent(True) self.con_view.set_show_line_numbers(False) # self.commands_view.set_show_right_margin(True) self.con_view.set_highlight_current_line(True) self.con_view.set_indent_on_tab(True) self.con_view.set_insert_spaces_instead_of_tabs(True) self.con_buffer = self.con_view.get_buffer() self.con_buffer.set_language(lngg) # self.commands_buffer.set_style_scheme(self.board.current_scheme) self.con_scrolled_window.add(self.con_view)