def create_instructions_frame(self): """ Instructions frame """ frame = tk.Frame(self, bg=config.COLOR_4) self.instructions_label = tk.Label( frame, text=_("Instructions"), anchor="w", font=font.Font(family=config.FONT, size=12), justify="left", fg=config.COLOR_0, bg=config.COLOR_4, ) self.instructions_label.pack() self.instructions = tk.Label( frame, font=font.Font(family=config.FONT, size=10), text= _("Left-click drag: move node\nDouble-click: properties\nMiddle-click: remove" ), anchor="w", fg=config.COLOR_11, bg=config.COLOR_4, ) self.instructions.pack() return frame
def __init__(self, parent, height=None, width=None): super().__init__(parent, height=height, width=width) self.parent = parent self.configure(bg=config.COLOR_4, ) # Activities self.activities_frame = ActivitySelectionFrame(self, bg=config.COLOR_4) self.activities_frame.place(relx=0, rely=0, relheight=0.65, relwidth=1) HelpButton( self, message= _("Activities are the basic building blocks of Automagica. By tieing activities together, you get a Flow." ), ).place(relx=1, rely=0, anchor="ne") # Nodes self.nodes_frame = self.create_nodes_frame() self.nodes_frame.place(relx=0, rely=0.65, relheight=0.20, relwidth=1) HelpButton( self, message= _("Special nodes allow you to control the way the Flow runs. It also allows you to extend the capabilities of your Flow beyond the activities that Automagica has to offer." ), ).place(relx=1, rely=0.65, anchor="ne") # Instructions self.instructions_frame = self.create_instructions_frame() self.instructions_frame.place(relx=0, rely=0.85, relheight=0.1, relwidth=1)
def clicked_open_button(self): """ Open file """ from .windows import NotificationWindow file_path = filedialog.askopenfilename( initialdir="./", title=_("Select Automagica Flow"), filetypes=[("Flow (.json)", "*.json")], ) if not file_path: return self.parent.master.file_path = file_path # Clear flow self.parent.master.flow_frame.clear() # Load flow self.parent.master.flow.load(self.parent.master.file_path) # Update title self.parent.master.title("{} - Automagica Flow".format( self.parent.master.file_path)) # Render flow self.parent.master.flow_frame.draw() NotificationWindow(self, _("Flow opened."))
def layout(self): self.ctrl = tk.BooleanVar() self.ctrl_checkbutton = tk.Checkbutton(self, text=_("Control"), variable=self.ctrl) self.ctrl_checkbutton.pack(side="left") if "ctrl" in self.key_combination: self.ctrl_checkbutton.select() self.shift = tk.BooleanVar() self.shift_checkbutton = tk.Checkbutton(self, text=_("Shift"), variable=self.shift) self.shift_checkbutton.pack(side="left") if "shift" in self.key_combination: self.shift_checkbutton.select() self.alt = tk.BooleanVar() self.alt_checkbutton = tk.Checkbutton(self, text=_("Alt"), variable=self.alt) self.alt_checkbutton.pack(side="left") if "alt" in self.key_combination: self.alt_checkbutton.select() keys = ["F1", "F2", "F3", "F4", "F5", "F6"] self.key_combobox = ttk.Combobox(self, values=keys) self.key_combobox.pack(side="left") if self.key_combination: self.key_combobox.current(keys.index(self.key_combination[-1]))
def create_buttons_frame(self): """ Buttons frame """ frame = tk.Frame(self, bg=config.COLOR_4) self.reset_bot_button = Button(frame, text=_("Reset Bot"), command=self.on_reset_bot_clicked) self.reset_bot_button.configure(font=(config.FONT, 8)) self.reset_bot_button.pack(side="left") self.clear_button = Button(frame, text=_("Clear Output"), command=self.on_clear_clicked) self.clear_button.configure(font=(config.FONT, 8)) self.clear_button.pack(side="left", padx=5) self.open_variable_explorer_button = Button( frame, text=_("Variable Explorer"), command=self.on_open_variable_explorer_clicked, ) self.open_variable_explorer_button.configure(font=(config.FONT, 8)) self.open_variable_explorer_button.pack(side="left") return frame
def on_right_click(self, event): """ Show menu on right-click """ # Create a menu self.menu = tk.Menu( self.parent, tearoff=0, font=(config.FONT, 10), bd=0 ) # Add menu items self.menu.add_command( label=_("Delete"), command=self.on_delete_clicked ) self.menu.add_command( label=_("Duplicate"), command=self.on_duplicate_clicked ) self.menu.add_command( label=_("Group into Sub-flow"), command=self.on_duplicate_clicked ) # If pop-up cannot be shown for some reason, release control to the parent try: self.menu.tk_popup(event.x_root, event.y_root) finally: self.menu.grab_release()
def save(self, file_path): logging.debug(_("Saving to {}").format(file_path)) with open(file_path, "w") as f: json.dump(self.to_dict(), f, indent=4) logging.info(_("Saved to {}").format(file_path)) self.file_path = file_path
def browse_button_click(self): if self.filetypes: file_path = filedialog.askopenfilename(initialdir="./", title=_("Select File"), filetypes=self.filetypes) else: file_path = filedialog.askopenfilename(initialdir="./", title=_("Select File")) self._set('"{}"'.format(file_path))
def load(self, file_path): self.file_path = file_path logging.debug(_("Loading Flow from {}").format(file_path)) with open(file_path, "r") as f: data = json.load(f) self.from_dict(data) logging.info(_("Loaded Flow from {}").format(file_path))
def __init__(self, parent, *args, **kwargs): super().__init__(parent, *args, **kwargs) from automagica.utilities import all_activities self.activities = all_activities() self.nodes_label = tk.Label( self, text=_("Activities"), anchor="w", justify="left", font=font.Font(family=config.FONT, size=12), fg=config.COLOR_0, bg=config.COLOR_4, ) self.nodes_label.pack() self.query = tk.StringVar() self.search_entry = InputField( self, textvariable=self.query, placeholder=_("Search activities..."), ) self.query.trace("w", self.search_activities) self.search_entry.focus() self.search_entry.bind("<Return>", self.search_activities) self.search_entry.pack(fill="x") self.canvas = tk.Canvas( self, bg="white", scrollregion=(0, 0, 300, 35 * len(self.activities)), ) self.render_activity_blocks( [val for key, val in self.activities.items()] ) self.vertical_scrollbar = tk.Scrollbar(self, orient=tk.VERTICAL) self.vertical_scrollbar.pack(side=tk.RIGHT, fill=tk.Y) self.vertical_scrollbar.config(command=self.canvas.yview) self.canvas.config(yscrollcommand=self.vertical_scrollbar.set) self.canvas.pack(side=tk.LEFT, expand=True, fill=tk.BOTH) self.bind("<Enter>", self._bound_to_mousewheel) self.bind("<Leave>", self._unbound_to_mousewheel)
def search_activities(self, *args): """ Search for activities by their keywords, name or description """ query = self.search_entry.get() # Clean query query = query.strip() query = query.lower() # Clear canvas self.canvas.delete("all") results = [] for key, val in self.activities.items(): if (any([query in keyword.lower() for keyword in val["keywords"]]) # Matches keywords or query in val["name"].lower() # Matches name or query in val["description"].lower() # Matches description or query == _("Search activities...").lower()): if val.get("class"): name = "{} - {}".format(val["class"], val["name"]) else: name = val["name"] results.append(val) self.render_activity_blocks(results)
def right_click(self, event): self.menu = tk.Menu(self.parent, tearoff=0, font=(config.FONT, 10)) self.menu.add_command(label=_("Delete"), command=self.delete_clicked) try: self.menu.tk_popup(event.x_root, event.y_root) finally: self.menu.grab_release()
def create_console_frame(self): """ Console frame """ frame = tk.Frame(self, bg=config.COLOR_4) self.command_entry = InputField( frame, placeholder=_("Type command here and press <ENTER> to run..."), ) self.command_entry.configure(font=(config.FONT_MONO, "10")) self.command_entry.bind("<Return>", self.on_enter_pressed) self.command_entry.bind("<Up>", self.on_up_press) self.console_text = scrolledtext.ScrolledText( frame, wrap=tk.WORD, relief=tk.FLAT, bg=config.COLOR_5, fg=config.COLOR_1, insertbackground=config.COLOR_1, ) self.console_text.frame.configure(bd=0, highlightthickness=0, relief="ridge") self.command_entry.pack(fill="x", anchor="nw") self.console_text.pack(fill="both", padx=0, pady=0, expand=True) self.console_text.insert( "1.0", _("Welcome to Automagica Flow! \nUse this Interactive Console to your liking!\n\n" ), ) self.console_text.configure(font=(config.FONT_MONO, "10"), state="disabled") self.console_text.tag_config("error", foreground=config.COLOR_14) self.line_start = 0 return frame
def layout(self): self.input_field = InputField(self) self.input_field.pack(side="left") self.browse_button = Button(self, text=_("View"), command=self.view_button_click, font=(config.FONT, 10)) self.browse_button.pack(side="left") self.record_button = Button( self, text=_("Record"), command=self.record_button_click, font=(config.FONT, 10), ) self.record_button.pack(side="left") if self.value: self._set(self.value)
def layout(self): self.input_field = InputField(self) self.input_field.pack(side="left") self.browse_button = Button(self, text=_("Browse"), command=self.browse_button_click) self.browse_button.pack(side="left") if self.value: self._set(self.value)
def layout(self): self.input_field = InputField(self) self.input_field.pack(side="left") self.browse_button = Button( self, text=_("Browse"), command=self.browse_button_click ) # Causes freezes on some platforms # self.browse_button.pack(side="left") if self.value: self._set(self.value)
def create_nodes_frame(self): """ Nodes frame """ frame = tk.Frame(self, bg=config.COLOR_4) from automagica.config import _ self.nodes_label = tk.Label( frame, text=_("Special Nodes"), anchor="w", justify="left", font=font.Font(family=config.FONT, size=12), fg=config.COLOR_0, bg=config.COLOR_4, ) self.nodes_label.pack() self.nodes_list = tk.Listbox(frame) self.nodes_list.bind( "<B1-Leave>", lambda event: "break") # Disable horizontal scrollling self.nodes_list.configure( bd=0, relief="flat", selectbackground=config.COLOR_0, selectforeground=config.COLOR_1, highlightthickness=0, fg=config.COLOR_11, bg=config.COLOR_10, activestyle="none", font=font.Font(family=config.FONT, size=10), ) self.node_types = [ ("Start", _("Start")), ("IfElse", _("If Else")), ("Loop", _("Loop")), ("DotPyFile", _("Python Script (.py)")), ("SubFlow", _("Sub-flow")), ("PythonCode", _("Python Code")), ] for _, label in self.node_types: self.nodes_list.insert(tk.END, label) self.nodes_list.bind("<Double-Button-1>", lambda e: self.select_node()) self.nodes_list.pack(fill="both", expand=True, padx=5, pady=5) return frame
def __init__(self, file_path=None, nodes=[], name=None): self.file_path = file_path self.nodes = nodes if not name: name = _("Unnamed Flow") self.name = name if self.file_path: self.load(self.file_path) else: node = StartNode(x=100, y=100) self.nodes.append(node)
def __init__(self, *args, message="", title="", **kwargs): super().__init__(*args, **kwargs) self.message = message if not title: title = _("Information") self.title = title self.configure( image=ICONS.tkinter("question-circle.png"), command=self.on_clicked, relief=tk.FLAT, bg=self.master.cget("bg"), # Take parent's background takefocus=False, # Do not include in 'TAB'-ing )
def browse_button_click(self): file_path = filedialog.askdirectory(initialdir="./", title=_("Select Directory")) self._set('"{}"'.format(file_path))
def __init__(self, parent, height=None, width=None): super().__init__(parent, height=height, width=width) self.configure(bg=config.COLOR_0) self.parent = parent logo_canvas = tk.Canvas( self, bg=config.COLOR_0, width=175, height=45, bd=0, highlightthickness=0, ) logo_canvas.pack(side="left", padx=10, pady=10) logo_path = os.path.join( os.path.abspath(__file__).replace( os.path.basename(os.path.realpath(__file__)), ""), "icons", "logo.png", ) self.logo_image = ImageTk.PhotoImage(file=logo_path) logo_canvas.create_image(0, 0, image=self.logo_image, anchor="nw") file_frame = ToolbarLabelFrame(self, text=_("File")) open_button = ToolbarImageButton( file_frame, text="Open", command=self.clicked_open_button, image_path="folder-open.png", ) self.parent.master.bind("<Alt-o>", lambda e: self.clicked_open_button()) open_button.pack(side="left") save_as_button = ToolbarImageButton( file_frame, text=_("Save As"), command=self.clicked_save_as_button, image_path="save.png", ) self.parent.master.bind("<Alt-a>", lambda e: self.clicked_save_as_button()) save_as_button.pack(side="left") file_frame.pack(side="left", padx=20, pady=5) run_frame = ToolbarLabelFrame(self, text=_("Run")) run_button = ToolbarImageButton( run_frame, text=_("Run Flow"), command=self.clicked_run_button, image_path="play-solid.png", ) run_button.config(bg=config.COLOR_7) self.parent.master.bind("<F5>", lambda e: self.clicked_run_button()) run_button.pack(side="left") run_step_by_step_button = ToolbarImageButton( run_frame, text=_("Step-by-Step"), command=self.clicked_run_step_by_step_button, image_path="google-play.png", ) self.parent.master.bind( "<Shift-F5>", lambda e: self.clicked_run_step_by_step_button()) run_step_by_step_button.pack(side="left") run_frame.pack(side="left", padx=20, pady=5) wand_frame = ToolbarLabelFrame( self, text=_("Automagica Wand (Powered by AI)")) record_click_button = ToolbarImageButton( wand_frame, image_path="mouse-pointer-solid.png", text=_("Click"), command=lambda: self.clicked_record_action_button( "automagica.activities.click"), ) record_click_button.pack(side="left") record_double_click_button = ToolbarImageButton( wand_frame, image_path="mouse-pointer-solid.png", text=_("Double-Click"), command=lambda: self.clicked_record_action_button( "automagica.activities.double_click"), ) record_double_click_button.pack(side="left") record_right_click_button = ToolbarImageButton( wand_frame, image_path="mouse-pointer-solid.png", text=_("Right-Click"), command=lambda: self.clicked_record_action_button( "automagica.activities.right_click"), ) record_right_click_button.pack(side="left") record_move_to_button = ToolbarImageButton( wand_frame, image_path="mouse-solid.png", text=_("Move To"), command=lambda: self.clicked_record_action_button( "automagica.activities.move_mouse_to"), ) record_move_to_button.pack(side="left") record_type_into_button = ToolbarImageButton( wand_frame, image_path="mouse-solid.png", text=_("Typing"), command=lambda: self.clicked_record_action_button( "automagica.activities.typing"), ) record_type_into_button.pack(side="left") record_read_text_button = ToolbarImageButton( wand_frame, image_path="glasses-solid.png", text=_("Read Text"), command=lambda: self.clicked_record_action_button( "automagica.activities.read_text"), ) record_read_text_button.pack(side="left") record_is_visible_button = ToolbarImageButton( wand_frame, image_path="eye.png", text=_("Is Visible"), command=lambda: self.clicked_record_action_button( "automagica.activities.is_visible"), ) record_is_visible_button.pack(side="left") record_wait_appear_button = ToolbarImageButton( wand_frame, image_path="eye.png", text=_("Wait Appear"), command=lambda: self.clicked_record_action_button( "automagica.activities.wait_appear"), ) record_wait_appear_button.pack(side="left") record_wait_vanish_button = ToolbarImageButton( wand_frame, image_path="eye.png", text=_("Wait Vanish"), command=lambda: self.clicked_record_action_button( "automagica.activities.wait_vanish"), ) record_wait_vanish_button.pack(side="left") wand_frame.pack(side="left", padx=20, pady=5) wand_settings_frame = ToolbarLabelFrame(self, text=_("Settings")) self.delay_menu = SettingContextMenu( wand_settings_frame, text="Delay", options=[ ("No Delay", 0), ("1 second", 1), ("2 seconds", 2), ("3 seconds", 3), ("4 seconds", 4), ("5 seconds", 5), ], ) self.delay_menu.pack(side="left") def my_ui_elements_button_clicked(): import webbrowser webbrowser.open( os.environ.get("AUTOMAGICA_PORTAL_URL", "https://portal.automagica.com") + "/ui-element/") self.my_ui_elements_button = ToolbarImageButton( wand_settings_frame, text="My UI Elements", image_path="magic-solid.png", command=my_ui_elements_button_clicked, ) self.my_ui_elements_button.pack(side="left") wand_settings_frame.pack(side="left", padx=5, pady=5)
def draw(self): from_, to_ = shortest_distance( self.from_nodegraph.connector_points, self.to_nodegraph.connector_points, ) self.line = self.parent.canvas.create_line( from_[0], from_[1], to_[0], to_[1], arrow="last", fill=self.fill, width=6, smooth=True, arrowshape="10 12 5", tags=("arrows"), ) if self.connector_type == "next_node" and isinstance( self.from_nodegraph, IfElseNodeGraph ): self.text_rectangle = self.parent.canvas.create_rectangle( ( (self.from_nodegraph.center_x + self.to_nodegraph.center_x) / 2 ) - 20, ( ( (self.from_nodegraph.node.y + self.from_nodegraph.h) + self.to_nodegraph.node.y ) / 2 ) - 10, ( (self.from_nodegraph.center_x + self.to_nodegraph.center_x) / 2 ) + 20, ( ( (self.from_nodegraph.node.y + self.from_nodegraph.h) + self.to_nodegraph.node.y ) / 2 ) + 10, fill=config.COLOR_7, outline=config.COLOR_7, ) self.text = self.parent.canvas.create_text( (self.from_nodegraph.center_x + self.to_nodegraph.center_x) / 2, ( (self.from_nodegraph.node.y + self.from_nodegraph.h) + self.to_nodegraph.node.y ) / 2, text=_("Yes"), fill=config.COLOR_1, font=(config.FONT, 8), ) if self.connector_type == "next_node" and isinstance( self.from_nodegraph, LoopNodeGraph ): self.text_rectangle = self.parent.canvas.create_rectangle( ( (self.from_nodegraph.center_x + self.to_nodegraph.center_x) / 2 ) - 20, ( ( (self.from_nodegraph.node.y + self.from_nodegraph.h) + self.to_nodegraph.node.y ) / 2 ) - 10, ( (self.from_nodegraph.center_x + self.to_nodegraph.center_x) / 2 ) + 20, ( ( (self.from_nodegraph.node.y + self.from_nodegraph.h) + self.to_nodegraph.node.y ) / 2 ) + 10, fill=config.COLOR_7, outline=config.COLOR_7, ) self.text = self.parent.canvas.create_text( (self.from_nodegraph.center_x + self.to_nodegraph.center_x) / 2, ( (self.from_nodegraph.node.y + self.from_nodegraph.h) + self.to_nodegraph.node.y ) / 2, text=_("Done"), fill=config.COLOR_1, ) if self.connector_type == "else_node": self.text_rectangle = self.parent.canvas.create_rectangle( ( (self.from_nodegraph.center_x + self.to_nodegraph.center_x) / 2 ) - 20, ( ( (self.from_nodegraph.node.y + self.from_nodegraph.h) + self.to_nodegraph.node.y ) / 2 ) - 10, ( (self.from_nodegraph.center_x + self.to_nodegraph.center_x) / 2 ) + 20, ( ( (self.from_nodegraph.node.y + self.from_nodegraph.h) + self.to_nodegraph.node.y ) / 2 ) + 10, fill=config.COLOR_6, outline=config.COLOR_6, ) self.text = self.parent.canvas.create_text( (self.from_nodegraph.center_x + self.to_nodegraph.center_x) / 2, ( (self.from_nodegraph.node.y + self.from_nodegraph.h) + self.to_nodegraph.node.y ) / 2, text=_("No"), fill=config.COLOR_1, ) if self.connector_type == "on_exception_node": self.text_rectangle = self.parent.canvas.create_rectangle( ( (self.from_nodegraph.center_x + self.to_nodegraph.center_x) / 2 ) - 20, ( ( (self.from_nodegraph.node.y + self.from_nodegraph.h) + self.to_nodegraph.node.y ) / 2 ) - 10, ( (self.from_nodegraph.center_x + self.to_nodegraph.center_x) / 2 ) + 20, ( ( (self.from_nodegraph.node.y + self.from_nodegraph.h) + self.to_nodegraph.node.y ) / 2 ) + 10, fill="orange", outline="orange", ) self.text = self.parent.canvas.create_text( (self.from_nodegraph.center_x + self.to_nodegraph.center_x) / 2, ( (self.from_nodegraph.node.y + self.from_nodegraph.h) + self.to_nodegraph.node.y ) / 2, text=_("Error"), fill=config.COLOR_1, ) if self.connector_type == "loop_node": self.text_rectangle = self.parent.canvas.create_rectangle( ( (self.from_nodegraph.center_x + self.to_nodegraph.center_x) / 2 ) - 20, ( ( (self.from_nodegraph.node.y + self.from_nodegraph.h) + self.to_nodegraph.node.y ) / 2 ) - 10, ( (self.from_nodegraph.center_x + self.to_nodegraph.center_x) / 2 ) + 20, ( ( (self.from_nodegraph.node.y + self.from_nodegraph.h) + self.to_nodegraph.node.y ) / 2 ) + 10, fill=config.COLOR_3, outline=config.COLOR_3, ) self.text = self.parent.canvas.create_text( (self.from_nodegraph.center_x + self.to_nodegraph.center_x) / 2, ( (self.from_nodegraph.node.y + self.from_nodegraph.h) + self.to_nodegraph.node.y ) / 2, text=_("Repeat"), fill=config.COLOR_1, )
def text_label(self): return self.node.label if self.node.label else _("Start")
def text_label(self): return self.node.label if self.node.label else _("If Else")
def text_label(self): return self.node.label if self.node.label else _("Loop")
def text_label(self): return self.node.label if self.node.label else _(".py file")
def browse_button_click(self): file_path = filedialog.asksaveasfilename(initialdir="./", title=_("Select File")) self._set('"{}"'.format(file_path))
def text_label(self): return self.node.label if self.node.label else _("Python code")
from automagica.config import Config, _ from automagica.gui.apps import ( AutomagicaTk, BotApp, FlowApp, LabApp, ScriptApp, TraceApp, WandApp, ) __version__ = "3.2.2" @click.group(help=_("Automagica v") + __version__) def cli(): """ Main CLI group """ pass @cli.command(help=_("Configure Automagica")) def configure(): """ 'automagica configure' launches the configuration wizard """ config = Config() config.wizard()
def text_label(self): return self.node.label if self.node.label else _("Comment")