コード例 #1
0
class Gui(GuiBase):
    """Application GUI and operations"""
    def __init__(self):
        super().__init__(logging.getLogger('p123'))

        self._auth = None
        self._main = None

        config_file = 'config.ini'
        if platform.system() == 'Darwin':
            app_user_folder = '{}/Library/Preferences/DataMiner'.format(
                Path.home())
            Path(app_user_folder).mkdir(parents=True, exist_ok=True)
            config_file = app_user_folder + '/' + config_file
        elif platform.system() == 'Windows' or platform.system() == 'Linux':
            app_user_folder = '{}/DataMiner'.format(Path.home())
            Path(app_user_folder).mkdir(parents=True, exist_ok=True)
            config_file = app_user_folder + '/' + config_file
        self._config = Config(self._logger, config_file)

        self._operation = None

        self._window.title(f'{cons.NAME} - v{cons.VERSION}')
        self._window.rowconfigure(0, weight=1, minsize=500)
        self._window.columnconfigure(0, weight=1, minsize=800)

        threading.Thread(target=self._init_api_client).start()

        self._window.bind_class('Entry', '<<Paste>>', custom_paste)
        self._window.bind_class('Text', '<<Paste>>', custom_paste)
        self._window.bind_class('ScrolledText', '<<Paste>>', custom_paste)
        self._window.protocol('WM_DELETE_WINDOW', self._on_close)
        self._window.mainloop()

    def _init_api_client(self):
        api_id = self._config.get('API', 'id') if self._config.has_option(
            'API', 'id') else None
        api_key = self._config.get('API', 'key') if self._config.has_option(
            'API', 'key') else None
        if api_id and api_key:
            self._api_client = Client(api_id=api_id, api_key=api_key)
            self._api_client.set_timeout(3600)
            if self._config.has_option('API', 'endpoint'):
                endpoint = self._config.get('API', 'endpoint')
                if endpoint:
                    self._api_client.set_endpoint(endpoint)

            frame = ttk.Frame(self._window)
            frame.grid(row=0, column=0, sticky='NSEW')
            ttk.Label(frame, text='Authenticating...').pack(expand=1)
            try:
                self._api_client.auth()
            except ClientException:
                self._api_client = None
                self._config.remove_option('API', 'key')
                self._config.save()
            frame.destroy()
        else:
            self._api_client = None

        if self._api_client:
            self._build_main_frame()
        else:
            self._build_auth_frame()

    # noinspection PyUnusedLocal
    def _on_close(self, *args):
        if self._api_client and self._main and self._main.get('input_changed'):
            if not messagebox.askokcancel(
                    'Quit',
                    'Unsaved changes will be lost, are you sure you want to continue?'
            ):
                return
        if self._operation:
            self._operation.pause()
        self.on_close()

    # noinspection PyUnusedLocal
    def _auth_submit(self, event=None):
        if self._auth['submit_in_progress']:
            return
        api_id = self._auth['api_id_var'].get()
        api_key = self._auth['api_key_var'].get()
        if not api_id or not api_key:
            messagebox.showwarning(message='API ID and key are required')
            return
        self._auth['submit_in_progress'] = True
        self._auth['submit_btn'].configure(text='Please wait...')
        self._auth['submit_btn'].configure(state=tk.DISABLED)
        threading.Thread(target=self._auth_check_credentials,
                         args=(api_id, api_key)).start()

    def _auth_check_credentials(self, api_id, api_key):
        self._api_client = Client(api_id=api_id, api_key=api_key)
        self._api_client.set_timeout(3600)
        if self._config.has_option('API', 'endpoint'):
            endpoint = self._config.get('API', 'endpoint')
            if endpoint:
                self._api_client.set_endpoint(endpoint)

        error = None
        try:
            self._api_client.auth()
        except ClientException as e:
            error = e
        self._auth['submit_btn'].configure(state=tk.NORMAL)
        self._auth['submit_btn'].configure(text='Confirm')
        if error:
            messagebox.showwarning(message=error)
        else:
            if self._auth['save_credentials_var'].get():
                if not self._config.has_section('API'):
                    self._config.add_section('API')
                self._config.set('API', 'id', api_id)
                self._config.set('API', 'key', api_key)
                self._config.save()
            self._build_main_frame()
            self._auth['save_credentials_var'].set(0)
            self._auth['api_id_var'].set('')
            self._auth['api_key_var'].set('')
        self._auth['submit_in_progress'] = False

    def _build_auth_frame(self):
        if self._auth is None:
            self._auth = {
                'save_credentials_var': tk.IntVar(),
                'api_id_var': tk.StringVar(),
                'api_key_var': tk.StringVar(),
                'frame': ttk.Frame(self._window),
                'submit_in_progress': False
            }
            self._auth['frame'].grid(row=0, column=0, sticky='NSEW')
            self._auth['frame'].rowconfigure(0, weight=1)
            self._auth['frame'].rowconfigure(1, weight=0)
            self._auth['frame'].rowconfigure(2, weight=0)
            self._auth['frame'].rowconfigure(3, weight=1)
            self._auth['frame'].columnconfigure(0, weight=1)
            self._auth['frame'].columnconfigure(1, weight=1)
            ttk.Label(self._auth['frame'], text='Api ID').grid(row=0,
                                                               column=0,
                                                               sticky='SE')
            self._auth['api_id_entry'] = ttk.Entry(
                self._auth['frame'],
                width=40,
                textvariable=self._auth['api_id_var'])
            self._auth['api_id_entry'].grid(row=0,
                                            column=1,
                                            sticky='SW',
                                            padx=5)
            ttk.Label(self._auth['frame'], text='Api Key').grid(row=1,
                                                                column=0,
                                                                sticky='E',
                                                                pady=5)
            api_key_entry = ttk.Entry(self._auth['frame'],
                                      width=40,
                                      textvariable=self._auth['api_key_var'])
            api_key_entry.grid(row=1, column=1, sticky='W', padx=5, pady=5)
            checkbox = ttk.Checkbutton(self._auth['frame'],
                                       text='remember credentials',
                                       var=self._auth['save_credentials_var'])
            checkbox.grid(row=2, column=1, sticky='W', padx=5)
            self._auth['submit_btn'] = ttk.Button(self._auth['frame'],
                                                  text='Confirm',
                                                  command=self._auth_submit)
            self._auth['submit_btn'].grid(row=3,
                                          column=1,
                                          sticky='NW',
                                          padx=5,
                                          pady=5)

            self._auth['api_id_entry'].bind('<Return>', self._auth_submit)
            api_key_entry.bind('<Return>', self._auth_submit)
            checkbox.bind('<Return>', self._auth_submit)
            self._auth['submit_btn'].bind('<Return>', self._auth_submit)

        self._auth['frame'].tkraise()
        self._auth['api_id_entry'].focus()

    def _build_main_frame(self):
        if self._main is None:
            self._main = {
                'frame': ttk.Frame(self._window, padding=(5, 10, 5, 5))
            }

            self._main['frame'].grid(row=0, column=0, sticky='NSEW')

            self._main['paned_window'] = ttk.PanedWindow(self._main['frame'],
                                                         orient=tk.VERTICAL)
            self._main['paned_window'].place(relwidth=1, relheight=1)

            main_frame = ttk.Frame(self._main['paned_window'])
            self._main['paned_window'].add(main_frame, weight=2)

            inner_frame = ttk.Frame(main_frame)
            inner_frame.place(relwidth=1, relheight=1)
            inner_frame.rowconfigure(0, weight=0)
            inner_frame.rowconfigure(1, weight=1)
            inner_frame.columnconfigure(0, weight=1)

            frame = ttk.Frame(inner_frame, padding=(0, 0, 0, 10))
            frame.grid(row=0, column=0, sticky='W')
            self._main['btn_validate'] = ttk.Button(
                frame, text='Validate', command=self._validate_input)
            self._main['btn_validate'].pack(side=tk.LEFT)
            ttk.Separator(frame, orient='vertical').pack(side=tk.LEFT,
                                                         fill='y',
                                                         padx=5,
                                                         pady=3)
            self._main['btn_execute'] = ttk.Button(frame,
                                                   text='Execute',
                                                   command=self._execute)
            self._main['btn_execute'].pack(side=tk.LEFT)
            self._main['btn_execute_stop'] = ttk.Button(
                frame, text='Stop', command=self._execute_stop)
            self._main['btn_execute_stop'].pack_forget()
            ttk.Separator(frame, orient='vertical').pack(side=tk.LEFT,
                                                         fill='y',
                                                         padx=5,
                                                         pady=3)
            self._main['btn_copy_output'] = ttk.Button(
                frame, text='Copy output', command=self._copy_output)
            self._main['btn_copy_output'].pack(side=tk.LEFT)
            self._main['btn_save_output'] = ttk.Button(
                frame, text='Save output', command=self._save_output)
            self._main['btn_save_output'].pack(side=tk.LEFT, padx=(5, 0))

            self._auto_save_output_init()
            ttk.Checkbutton(frame,
                            text='Auto save output',
                            variable=self._auto_save,
                            command=self._auto_save_output_toggle).pack(
                                side=tk.LEFT, padx=(5, 0))

            self._main['btn_auto_save_output_folder'] = ttk.Label(
                frame,
                text='(' + self._auto_save_folder +
                ')' if self._auto_save.get() else '')
            self._main['btn_auto_save_output_folder'].pack(side=tk.LEFT)

            self._main['notebook'] = ttk.Notebook(inner_frame)
            self._main['notebook'].grid(row=1, column=0, sticky='NSEW')

            self._build_input_frame()
            self._build_output_frame()
            self._build_console_frame()

        self._main['frame'].tkraise()
        self._build_menu()

    def _auto_save_output_init(self):
        self._auto_save = tk.IntVar()
        if not self._config.has_section('OUTPUT'):
            self._config.add_section('OUTPUT')
        if self._config.has_option('OUTPUT', 'auto_save'):
            self._auto_save.set(1)
        self._auto_save_folder = self._config.get(
            'OUTPUT', 'auto_save_folder') if self._auto_save.get() else None

    def _auto_save_output_toggle(self, init: bool = True):
        if init:
            threading.Thread(target=self._auto_save_output_toggle,
                             args=[False]).start()
            return

        if not self._auto_save.get():
            self._config.remove_option('OUTPUT', 'auto_save')
        else:
            self._auto_save_folder = filedialog.askdirectory()
            if not self._auto_save_folder:
                self._auto_save.set(0)
                return
            self._config.set('OUTPUT', 'auto_save', 'yes')
            self._config.set('OUTPUT', 'auto_save_folder',
                             self._auto_save_folder)
        self._main['btn_auto_save_output_folder'].configure(
            text='(' + self._auto_save_folder +
            ')' if self._auto_save.get() else '')
        self._config.save()

    def _build_menu(self):
        save_callback = functools.partial(self._save_input, False, True)
        save_as_callback = functools.partial(self._save_input, True, True)

        if not self._main.get('menu_bar'):
            self._main['menu_bar'] = tk.Menu(self._window)

            input_menu = tk.Menu(self._main['menu_bar'], tearoff=0)
            input_menu.add_command(label='Open',
                                   underline=0,
                                   accelerator="Ctrl+O",
                                   command=self._open_input_file)
            input_menu.add_command(label='Close',
                                   underline=0,
                                   accelerator="Ctrl+Shift+C",
                                   command=self._close_input_file)
            input_menu.add_command(label='Save',
                                   underline=0,
                                   accelerator="Ctrl+S",
                                   command=save_callback)
            input_menu.add_command(label='Save As',
                                   underline=5,
                                   accelerator="Ctrl+Shift+S",
                                   command=save_as_callback)
            self._main['menu_bar'].add_cascade(label='Input',
                                               underline=0,
                                               menu=input_menu)

            try:
                samples_menu = tk.Menu(self._main['menu_bar'], tearoff=0)
                path = 'samples'
                for level1 in os.listdir(path):
                    entry = os.path.join(path, level1)
                    if os.path.isfile(entry):
                        samples_menu.add_command(label=level1)
                    else:
                        regex = re.compile(rf'^{level1}(\s*-\s*)?|\.yaml$',
                                           re.IGNORECASE)
                        entries = os.listdir(entry)
                        submenu = tk.Menu(tearoff=0)
                        for level2 in entries:
                            name = regex.sub('', level2)
                            submenu.add_command(label=name,
                                                command=functools.partial(
                                                    self._open_input_file,
                                                    True,
                                                    os.path.join(
                                                        entry, level2), True))
                        samples_menu.add_cascade(label=level1, menu=submenu)
                self._main['menu_bar'].add_cascade(label='Samples',
                                                   underline=1,
                                                   menu=samples_menu)
            except Exception:
                pass

            session_menu = tk.Menu(self._main['menu_bar'], tearoff=0)
            session_menu.add_command(
                label=f'API ID: {self._api_client.get_api_id()}')
            session_menu.add_command(label='Logout',
                                     underline=0,
                                     accelerator="Ctrl+L",
                                     command=self._logout)
            session_menu.add_command(label='Quit',
                                     underline=0,
                                     accelerator="Ctrl+Q",
                                     command=self._on_close)
            self._main['menu_bar'].add_cascade(label='Session',
                                               underline=0,
                                               menu=session_menu)

            self._main['menu_input_open_cmd'] = (input_menu, 0)
            self._main['menu_input_close_cmd'] = (input_menu, 1)
            self._main['menu_input_save_cmd'] = (input_menu, 2)
            self._main['menu_input_save_as_cmd'] = (input_menu, 3)
            self._main['menu_session_api_id'] = (session_menu, 0)
            self._main['menu_session_logout'] = (session_menu, 1)
            self.toggle_state(self._main['menu_input_close_cmd'])
            self.toggle_state(self._main['menu_input_save_cmd'])
        else:
            self._main['menu_session_api_id'][0].entryconfig(
                self._main['menu_session_api_id'][1],
                label=f'API ID: {self._api_client.get_api_id()}')

        self._window.config(menu=self._main['menu_bar'])

        GuiBase.bind_ci(self._window,
                        True,
                        modifier='Control',
                        letter='o',
                        callback=self._open_input_file)
        GuiBase.bind_ci(self._window,
                        True,
                        modifier='Control-Shift',
                        letter='c',
                        callback=self._close_input_file)
        GuiBase.bind_ci(self._window,
                        True,
                        modifier='Control',
                        letter='s',
                        callback=save_callback)
        GuiBase.bind_ci(self._window,
                        True,
                        modifier='Control-Shift',
                        letter='s',
                        callback=save_as_callback)
        GuiBase.bind_ci(self._window,
                        True,
                        modifier='Control',
                        letter='l',
                        callback=self._logout)
        GuiBase.bind_ci(self._window,
                        True,
                        modifier='Control',
                        letter='q',
                        callback=self._on_close)

        GuiBase.bind_ci(self._window,
                        True,
                        modifier='Control-Shift',
                        letter='v',
                        callback=self._validate_input)
        GuiBase.bind_ci(self._window,
                        True,
                        modifier='Control',
                        letter='e',
                        callback=self._execute)

    # noinspection PyUnusedLocal
    def _close_input_file(self, init: bool = True):
        if self.state_disabled(self._main['menu_input_close_cmd']):
            return

        if init:
            if self._main.get('input_changed'):
                if not messagebox.askokcancel(
                        'Close file',
                        'Unsaved changes will be lost, are you sure you want to continue?'
                ):
                    return
            return threading.Thread(target=self._close_input_file,
                                    args=[False]).start()

        self._input_text_modified_event_toggle(False)
        self._main['input'].delete('1.0', tk.END)
        self._input_text_modified_event_toggle()

        self.toggle_state(self._main['menu_input_close_cmd'])
        self.toggle_state(self._main['menu_input_save_cmd'])
        del self._main['opened_file']
        self._update_displayed_file_name()

    def _destroy_menu(self):
        self._window.config(menu=tk.Menu(self._window))

        GuiBase.unbind_ci(self._window, True, modifier='Control', letter='o')
        GuiBase.unbind_ci(self._window,
                          True,
                          modifier='Control-Shift',
                          letter='c')
        GuiBase.unbind_ci(self._window, True, modifier='Control', letter='s')
        GuiBase.unbind_ci(self._window,
                          True,
                          modifier='Control-Shift',
                          letter='s')
        GuiBase.unbind_ci(self._window, True, modifier='Control', letter='l')
        GuiBase.unbind_ci(self._window, True, modifier='Control', letter='q')

        GuiBase.unbind_ci(self._window,
                          True,
                          modifier='Control-Shift',
                          letter='v')
        GuiBase.unbind_ci(self._window, True, modifier='Control', letter='e')

    # noinspection PyUnusedLocal
    def _logout(self, *args):
        if self.state_disabled(self._main['menu_session_logout']):
            return

        if self._config.has_section('API'):
            self._config.remove_option('API', 'id')
            self._config.remove_option('API', 'key')
            self._config.save()

        self._api_client = None
        self._destroy_menu()
        self._build_auth_frame()

    # noinspection PyUnusedLocal
    def _input_tab_to_spaces(self, arg):
        self._main['input'].insert(tk.INSERT, ' ' * 4)
        return 'break'

    def _build_input_frame(self):
        main_frame = ttk.Frame(self._main['notebook'], padding=5)
        self._main['notebook'].add(main_frame, text='Input')

        self._main['input'] = ScrolledTextLineNumbers(main_frame, undo=True)
        self._main['input'].pack(side='right', fill='both', expand=True)
        self._main['input'].bind('<Tab>', self._input_tab_to_spaces)
        self._main['input'].bind('<<Paste>>', self._handle_input_paste)
        self._input_text_modified_event_toggle()
        GuiBase.bind_ci(
            self._main['input'],
            modifier='Control',
            letter='o',
            callback=lambda x: self._open_input_file(True) or 'break')

    def _input_text_modified_event_toggle(self, state: bool = True):
        if self._main.get('input_text_modified_callback') is None:
            self._main['input_text_modified_callback'] = functools.partial(
                self._update_displayed_file_name, True)
        if state:
            if self._main.get('input_text_modified_unbind') is None:
                self._main['input_text_modified_unbind'] =\
                    self._main['input'].bind('<<TextModified>>', self._main['input_text_modified_callback'])
        elif self._main.get('input_text_modified_unbind'):
            self._main['input'].unbind(
                '<<TextModified>>', self._main['input_text_modified_unbind'])
            del self._main['input_text_modified_unbind']

    # noinspection PyUnusedLocal
    @staticmethod
    def _handle_input_paste(event):
        custom_paste(event,
                     str(event.widget.clipboard_get()).replace('\t', ' ' * 4))
        return 'break'

    # noinspection PyUnusedLocal
    def _input_undo(self, *args):
        try:
            self._main['input'].edit_undo()
        except tk.TclError:
            pass

    # noinspection PyUnusedLocal
    def _input_redo(self, *args):
        try:
            self._main['input'].edit_redo()
        except tk.TclError:
            pass

    def _build_output_frame(self):
        main_frame = ttk.Frame(self._main['notebook'], padding=5)
        self._main['notebook'].add(main_frame, text='Output')

        self._main['output'] = ScrolledTextHorizontal(main_frame,
                                                      state='disabled')
        self._main['output'].place(relwidth=1, relheight=1)

    def _build_console_frame(self):
        main_frame = ttk.LabelFrame(self._main['paned_window'],
                                    text='Console',
                                    padding=5)
        self._main['paned_window'].add(main_frame, weight=1)

        inner_frame = tk.Frame(main_frame)
        inner_frame.place(relwidth=1, relheight=1)
        inner_frame.rowconfigure(0, weight=0)
        inner_frame.rowconfigure(1, weight=1)
        inner_frame.columnconfigure(0, weight=1)

        frame = ttk.Frame(inner_frame, padding=(0, 0, 0, 5))
        frame.grid(row=0, column=0, sticky='W')
        ttk.Button(frame, text='Clear',
                   command=self._clear_console).pack(side=tk.LEFT)

        frame = ttk.Frame(inner_frame)
        frame.grid(row=1, column=0, sticky='NSEW')
        console = ScrolledText(frame, state='disabled')
        console.place(relwidth=1, relheight=1)
        self._logger.setLevel(logging.INFO)
        self._logger_handler = ConsoleLoggerHandler(console)
        self._logger.addHandler(self._logger_handler)

    def _save_output(self, init: bool = True, from_btn: bool = True):
        """
        Dumps content of the output (as csv) into user selected file.
        Calls itself in a separate thread to avoid blocking and blocks operations that might
        cause a lock.
        """
        if init:
            self.toggle_state(self._main['btn_execute'])
            self.toggle_state(self._main['btn_save_output'])
            threading.Thread(target=self._save_output,
                             args=[False, from_btn]).start()
            return

        try:
            rows = self._operation.get_result(
            ) if self._operation is not None else None
            if rows:
                if from_btn:
                    file = filedialog.asksaveasfilename(
                        defaultextension='.csv', filetypes=[('csv', '*.csv')])
                else:
                    file = self._auto_save_folder + '/' + str(self._operation.get_name()).lower() + '_' +\
                           datetime.datetime.now().strftime('%Y%m%d_%H%M%S') + '.csv'
                if file:
                    with open(file, 'w', newline='') as stream:
                        csv_writer = csv.writer(stream)
                        csv_writer.writerows(rows)
                        self._logger.info('Output saved to ' + file)
            else:
                self._logger.error('Output is empty')
        except OSError as e:
            self._logger.error(e)
        except Exception:
            print_to_log(traceback.format_exc())
            self._logger.error('Internal error')

        self.toggle_state(self._main['btn_execute'], True)
        self.toggle_state(self._main['btn_save_output'], True)

    def _copy_output(self, init: bool = True):
        """
        Dumps content of the output (tab separated) into the clipboard.
        Calls itself in a separate thread to avoid blocking and blocks operations that might
        cause a lock.
        """
        if init:
            self.toggle_state(self._main['btn_execute'])
            self.toggle_state(self._main['btn_copy_output'])
            threading.Thread(target=self._copy_output, args=[False]).start()
            return

        try:
            rows = self._operation.get_result(
            ) if self._operation is not None else None
            if rows:
                if len(rows) <= 1000:
                    data = ''
                    for row in rows:
                        data += '\t'.join(
                            str(val if val is not None else '')
                            for val in row) + '\n'
                    self._window.clipboard_clear()
                    self._window.clipboard_append(data.strip())
                    self._logger.info('Output copied to clipboard')
                else:
                    self._logger.warning(
                        'Output is too big, please use "Save output"')
            else:
                self._logger.error('Output is empty')
        except Exception:
            print_to_log(traceback.format_exc())
            self._logger.error('Internal error')

        self.toggle_state(self._main['btn_execute'], True)
        self.toggle_state(self._main['btn_copy_output'], True)

    # noinspection PyUnusedLocal
    def _save_input(self, save_as: bool = False, init: bool = True, *args):
        """
        Dumps content of the input scrolledtext element into user selected file.
        Calls itself in a separate thread to avoid blocking and blocks operations that might
        cause a lock.
        """
        if init:
            if not save_as and self.state_disabled(
                    self._main['menu_input_save_cmd']):
                save_as = True

            if save_as and self.state_disabled(
                    self._main['menu_input_save_as_cmd']):
                return

            self.toggle_state(self._main['menu_input_open_cmd'])
            self.toggle_state(self._main['menu_input_close_cmd'])
            self.toggle_state(self._main['menu_input_save_as_cmd' if save_as
                                         else 'menu_input_save_cmd'])
            self.toggle_state(self._main['input'])
            threading.Thread(target=self._save_input, args=[save_as,
                                                            False]).start()
            return

        try:
            content = self._main['input'].get('1.0', tk.END).strip()
            if content:
                if save_as:
                    file_path = filedialog.asksaveasfilename(
                        defaultextension='.yaml',
                        filetypes=[('yaml', '*.yaml')])
                else:
                    file_path = self._main['opened_file']['path']
                if file_path:
                    with open(file_path, 'w') as stream:
                        stream.write(content)
                    if save_as:
                        self.toggle_state(self._main['menu_input_save_cmd'],
                                          True)
                        self.toggle_state(self._main['menu_input_close_cmd'],
                                          True)
                        self._main['opened_file'] = {
                            'path': file_path,
                            'name': file_path.split('/')[-1]
                        }
                    self._update_displayed_file_name()
                    self._logger.info('Input saved to ' + file_path)
            else:
                self._logger.error('Input is empty')
        except OSError as e:
            self._logger.error(e)
        except Exception:
            print_to_log(traceback.format_exc())
            self._logger.error('Internal error')

        self.toggle_state(self._main['menu_input_open_cmd'], True)
        self.toggle_state(self._main['menu_input_close_cmd'], True)
        self.toggle_state(
            self._main['menu_input_save_as_cmd'
                       if save_as else 'menu_input_save_cmd'], True)
        self.toggle_state(self._main['input'], True)

    def _open_input_file(self,
                         init: bool = True,
                         file_path: str = None,
                         anonymous_mode: bool = False):
        """
        Opens user selected file and dumps its content into the input scrolledtext element.
        Calls itself in a separate thread to avoid blocking and blocks operations that might cause a lock.
        """
        if init:
            if self.state_disabled(self._main['menu_input_open_cmd']):
                return

            if self._main.get('input_changed'):
                if not messagebox.askokcancel(
                        'Open File',
                        'Unsaved changes will be lost, are you sure you want to continue?'
                ):
                    return

            self.toggle_state(self._main['menu_input_open_cmd'])
            self.toggle_state(self._main['menu_input_close_cmd'])
            self.toggle_state(self._main['menu_input_save_cmd'])
            self.toggle_state(self._main['menu_input_save_as_cmd'])
            self.toggle_state(self._main['btn_validate'])
            self.toggle_state(self._main['btn_execute'])
            self.toggle_state(self._main['input'])
            threading.Thread(target=self._open_input_file,
                             args=[False, file_path, anonymous_mode]).start()
            return

        try:
            if file_path is None:
                file_path = filedialog.askopenfilename(
                    filetypes=[('yaml', '*.yaml'), ('All Files', '*.*')])
            if file_path:
                with open(file_path) as stream:
                    if os.fstat(stream.fileno()).st_size <= 10000000:
                        content = stream.read().replace('\t', ' ' * 4)
                        self.toggle_state(self._main['input'], True)

                        self._input_text_modified_event_toggle(False)
                        self._main['input'].delete('1.0', tk.END)
                        self._main['input'].insert(tk.END, content)
                        self._input_text_modified_event_toggle()

                        self._logger.info(f'File "{file_path}" loaded')

                        if anonymous_mode:
                            if self._main.get('opened_file') is not None:
                                del self._main['opened_file']
                                self.toggle_state(
                                    self._main['menu_input_close_cmd'])
                                self.toggle_state(
                                    self._main['menu_input_save_cmd'])
                        else:
                            self._main['opened_file'] = {
                                'path': file_path,
                                'name': re.split(r'[\\/]', file_path)[-1]
                            }
                            self.toggle_state(
                                self._main['menu_input_close_cmd'], True)
                            self.toggle_state(
                                self._main['menu_input_save_cmd'], True)
                        self._update_displayed_file_name()
                        self._main['notebook'].select(0)
                    else:
                        self._logger.error('File is too big (max 10Mb)')
        except OSError as e:
            self._logger.error(e)
        except Exception:
            print_to_log(traceback.format_exc())
            self._logger.error('Internal error')

        self.toggle_state(self._main['menu_input_open_cmd'], True)
        self.toggle_state(self._main['menu_input_close_cmd'], True)
        self.toggle_state(self._main['menu_input_save_cmd'], True)
        self.toggle_state(self._main['menu_input_save_as_cmd'], True)
        self.toggle_state(self._main['btn_validate'], True)
        self.toggle_state(self._main['btn_execute'], True)
        self.toggle_state(self._main['input'], True)

    # noinspection PyUnusedLocal
    def _update_displayed_file_name(self, changed: bool = False, *args):
        if changed and self._main.get('input_changed'):
            return
        opened_file = self._main.get('opened_file')
        if opened_file:
            self._window.title('{} - v{} - {}{}'.format(
                cons.NAME, cons.VERSION, opened_file['name'],
                '*' if changed else ''))
        else:
            self._window.title('{} - v{}{}'.format(cons.NAME, cons.VERSION,
                                                   ' - *' if changed else ''))
        self._main['input_changed'] = changed

    def _validate_yaml_input(self) -> dict:
        try:
            data = self._main['input'].get('1.0', tk.END)
            data = yaml.safe_load(data)
            if operation.process_input(data=data, logger=self._logger):
                self._logger.info('Input validated')
                return data
        except yaml.YAMLError as e:
            self._logger.error(e)

    def _validate_input(self, init: bool = True):
        """
        Validates input. Calls itself in a separate thread to avoid blocking and blocks operations that might
        cause a lock.
        """
        if init:
            if self.state_disabled(self._main['btn_validate']):
                return

            self.toggle_state(self._main['menu_input_open_cmd'])
            self.toggle_state(self._main['menu_input_close_cmd'])
            self.toggle_state(self._main['btn_validate'])
            self.toggle_state(self._main['btn_execute'])
            self.toggle_state(self._main['input'])
            threading.Thread(target=self._validate_input, args=[False]).start()
            return

        try:
            self._validate_yaml_input()
        except Exception:
            print_to_log(traceback.format_exc())
            self._logger.error('Internal error')

        self.toggle_state(self._main['menu_input_open_cmd'], True)
        self.toggle_state(self._main['menu_input_close_cmd'], True)
        self.toggle_state(self._main['btn_validate'], True)
        self.toggle_state(self._main['btn_execute'], True)
        self.toggle_state(self._main['input'], True)

    def _execute(self, init: bool = True):
        """
        Validates input and runs backtests defined in it; supports pausing/resuming.
        Calls itself in a separate thread to avoid blocking and blocks operations that might cause a lock.
        """
        if init:
            if self.state_disabled(self._main['btn_execute']):
                return

            if self._operation is None or self._operation.is_finished():
                # start
                self.toggle_state(self._main['menu_input_open_cmd'])
                self.toggle_state(self._main['menu_input_close_cmd'])
                self.toggle_state(self._main['menu_session_logout'])
                self.toggle_state(self._main['btn_validate'])
                self._main['btn_execute'].configure(text='Pause')
                self.toggle_state(self._main['btn_copy_output'])
                self.toggle_state(self._main['btn_save_output'])
                self.toggle_state(self._main['input'])
            else:
                if not self._operation.is_paused():
                    # pause
                    self._operation.pause()
                    self.toggle_state(self._main['btn_execute'])
                    self._main['btn_execute'].configure(text='Pausing...')
                    return
                else:
                    # resume
                    self._operation.resume()
                    self._main['btn_execute'].configure(text='Pause')
                    self._main['btn_execute_stop'].pack_forget()
                    self._logger.info('Resumed')
            threading.Thread(target=self._execute, args=[False]).start()
            return

        try:
            if self._operation is None or self._operation.is_finished():
                data = self._validate_yaml_input()
                if data is not None:
                    self._main['output'].configure(state='normal')
                    self._main['output'].delete('1.0', tk.END)
                    self._main['output'].configure(state='disabled')
                    self._main['notebook'].select(1)
                    self._operation = operation.Operation.init(
                        api_client=self._api_client,
                        data=data,
                        output=self._main['output'],
                        logger=self._logger)
            if self._operation is not None and not self._operation.is_finished(
            ):
                self._operation.run()
        except Exception:
            print_to_log(traceback.format_exc())
            self._logger.error('Internal error')

        if self._operation is None or not self._operation.is_paused(
        ) or self._operation.is_finished():
            self._execute_stop(False)
        else:
            self.toggle_state(self._main['btn_execute'], True)
            self._main['btn_execute'].configure(text='Resume')
            self._main['btn_execute_stop'].pack(
                side=tk.LEFT, padx=(5, 0), after=self._main['btn_execute'])
            self._logger.info('Paused')

    def _execute_stop(self, from_btn: bool = True):
        self.toggle_state(self._main['menu_input_open_cmd'], True)
        self.toggle_state(self._main['menu_input_close_cmd'], True)
        self.toggle_state(self._main['menu_session_logout'], True)
        self.toggle_state(self._main['btn_validate'], True)
        self.toggle_state(self._main['btn_execute'], True)
        self._main['btn_execute'].configure(text='Execute')
        self._main['btn_execute_stop'].pack_forget()
        self.toggle_state(self._main['btn_copy_output'], True)
        self.toggle_state(self._main['btn_save_output'], True)
        self.toggle_state(self._main['input'], True)
        if from_btn:
            if self._operation is not None:
                self._operation.stop()
            self._logger.info('Stopped')
        elif self._auto_save.get():
            self._save_output(True, False)

    def _clear_console(self):
        self._logger_handler.clear()