Example #1
0
class InfoCheck(InfoMaster):
    def __init__(self, master, value, label, validate_cmd=None):
        super(InfoCheck, self).__init__(master, value, label, validate_cmd)

        #self._label = Label(self._master, text="{0}: ".format(data[0]))
        if OSCONST.os == 'LNX':
            self._value = tkCheckbutton(self._master,
                                        variable=value,
                                        command=self._validate_cmd)
        else:
            self._value = Checkbutton(self._master,
                                      variable=value,
                                      command=self._validate_cmd)

    def _set_value(self, value):
        self._value.config(variable=value)

    @property
    def validate_cmd(self):
        return self._validate_cmd

    @validate_cmd.setter
    def validate_cmd(self, value):
        # pass the new validate_cmd to the validated entry if required
        self._validate_cmd = value
        self._value.config(command=self._validate_cmd)
Example #2
0
    def add_category(self, category_name):
        ''' Adds a given category to this object. If the given
            category already exists, it won't be added again. Category
            names are assumed to be unique.

            Args:
                category_name (str): name of the category to add.
        '''
        if category_name not in self.category_objects.keys():
            categories = self.params.get_set_of_parameters(self.category_type)
            if category_name not in categories:
                categories.add(category_name)
                self.params.update_parameter(self.category_type,
                                             ';'.join(categories))
            self.nb_entries += 1
            row_index = self.nb_entries
            self.options[category_name] = [IntVar(), IntVar(), row_index]

            box = Label(self.frame_with_boxes,
                        text=category_name,
                        justify=LEFT,
                        wraplength=MIN_COLUMN_WIDTH_NAME - 10)
            box.grid(row=row_index, column=0, sticky=W, padx=7, pady=5)
            btn = []
            for count in range(2):
                check_btn = Checkbutton(
                    self.frame_with_boxes,
                    variable=self.options[category_name][count])
                check_btn.config(command=(
                    lambda count=count: self.on_select_box(box, count)))
                btn.append(check_btn)
                btn[count].grid(row=row_index, column=count + 1, pady=5)

            self.category_objects[category_name] = (box, btn[0], btn[1])
            nd_categories = self.params.get_set_of_parameters(
                'NON_DISCRETIONARY_CATEGORIES')
            if category_name in nd_categories:
                btn[0].invoke()

            wd_categories = self.params.get_set_of_parameters(
                'WEAKLY_DISPOSAL_CATEGORIES')
            if category_name in wd_categories:
                btn[1].invoke()
            self.update_scroll_region()
    def add_category(self, category_name):
        ''' Adds a given category to this object. If the given
            category already exists, it won't be added again. Category
            names are assumed to be unique.

            Args:
                category_name (str): name of the category to add.
        '''
        if category_name not in self.category_objects.keys():
            categories = self.params.get_set_of_parameters(self.category_type)
            if category_name not in categories:
                categories.add(category_name)
                self.params.update_parameter(self.category_type,
                                             ';'.join(categories))
            self.nb_entries += 1
            row_index = self.nb_entries
            self.options[category_name] = [IntVar(), IntVar(), row_index]

            box = Label(self.frame_with_boxes, text=category_name,
                        justify=LEFT, wraplength=MIN_COLUMN_WIDTH_NAME - 10)
            box.grid(row=row_index, column=0, sticky=W, padx=7, pady=5)
            btn = []
            for count in range(2):
                check_btn = Checkbutton(self.frame_with_boxes,
                                        variable=
                                        self.options[category_name][count])
                check_btn.config(command=(lambda count=count:
                                          self.on_select_box(box, count)))
                btn.append(check_btn)
                btn[count].grid(row=row_index, column=count + 1, pady=5)

            self.category_objects[category_name] = (box, btn[0], btn[1])
            nd_categories = self.params.get_set_of_parameters(
                'NON_DISCRETIONARY_CATEGORIES')
            if category_name in nd_categories:
                btn[0].invoke()

            wd_categories = self.params.get_set_of_parameters(
                'WEAKLY_DISPOSAL_CATEGORIES')
            if category_name in wd_categories:
                btn[1].invoke()
            self.update_scroll_region()
Example #4
0
	def open_light_settings( self, top, toplevel = 0 ):
		""" Opens a dialog for modifying the light settings """
		L = self.dir['canvas'].config("lighting")
		frame = tk.Frame(top)
		
		vars = [tk.IntVar() for val in range(len(L.settings))]
		[var.set(val) for var,val in zip(vars,L.settings.values())]
		def set(key,val):
			if key == 'texture': 
				self.dir['canvas'].texture = 1-self.dir['canvas'].texture
				L.settings[key] = self.dir['canvas'].texture
			elif key == 'fill': 
				self.dir['canvas'].fill = 1-self.dir['canvas'].fill
				L.settings[key] = self.dir['canvas'].fill
			else: L.settings[key] = 1-L.settings[key]
		
		for i,(key,val) in enumerate(L.settings.items()):				
			choice = Checkbutton(top,text=str(key),variable=vars[i])
			choice.config(command=lambda i=i,
				key=key:set(key,val))
			choice.grid(row=i,column=0,sticky='w')

		if toplevel:
			top.mainloop()
Example #5
0
class App(Tk):  #the main class for the main window
    def __init__(self):
        Tk.__init__(self)

        # window properties
        self.title(string="Screen Recorder")
        self.iconbitmap("icon.ico")
        self.resizable(width=False, height=False)

        ffmpegAvailable = False
        for item in os.listdir():
            if item == "ffmpeg.exe":
                ffmpegAvailable = True
                break
        if not ffmpegAvailable:
            self.withdraw()
            if messagebox.askyesno(
                    "FFmpeg Not Found",
                    "ffmpeg.exe could not be found in screen recorder's directory. Do you want to be redirected to the ffmpeg download website?"
            ):
                webbrowser.open_new_tab("https://ffmpeg.zeranoe.com/builds/")
            exit()
        self.cmdGen = cmdGen(
        )  # create a command generator object to store settings

        # file name
        label1 = Label(self, text="File Name:")
        label1.grid(row=0, column=0, sticky="")
        self.entry1 = Entry(self)
        self.entry1.grid(row=0, column=1, sticky="ew")

        # ensure the existance of the "ScreenCaptures" directory
        try:
            os.mkdir("ScreenCaptures")
        except FileExistsError:
            pass
        os.chdir("ScreenCaptures")

        # find a default file name that is currently available.
        defaultFile = "ScreenCapture.mp4"
        available = False
        fileNum = 0
        while available == False:
            hasMatch = False
            for item in os.listdir():
                if item == defaultFile:
                    hasMatch = True
                    break
            if not hasMatch:
                available = True
            else:
                fileNum += 1
                defaultFile = "ScreenCapture" + str(fileNum) + ".mp4"
        os.chdir("..")
        self.entry1.insert(END, defaultFile)

        # radio buttons determine what to record
        self.what = StringVar()
        self.what.set("desktop")
        self.radio2 = Radiobutton(self,
                                  text="record the window with the title of: ",
                                  variable=self.what,
                                  value="title",
                                  command=self.enDis1)
        self.radio1 = Radiobutton(self,
                                  text="record the entire desktop",
                                  variable=self.what,
                                  value="desktop",
                                  command=self.enDis)
        self.radio1.grid(row=1, column=0, sticky="w")
        self.radio2.grid(row=2, column=0, sticky="w")
        self.entry2 = Entry(self, state=DISABLED)
        self.entry2.grid(row=2, column=1, sticky="ew")

        # initialize webcam
        self.webcamdevices = Webcam.listCam()
        self.webcamrecorder = Webcam.capturer("")

        # "record from webcam" checkbox
        self.rcchecked = IntVar()
        self.recordcam = Checkbutton(self,
                                     text="Record from webcam",
                                     command=self.checkboxChanged,
                                     variable=self.rcchecked)
        self.recordcam.grid(row=3, column=0)

        # a drop-down allowing you to select the webcam device from the available directshow capture devices
        self.devicename = StringVar(self)
        if self.webcamdevices:
            self.devicename.set(self.webcamdevices[0])
            self.deviceselector = OptionMenu(self, self.devicename,
                                             *self.webcamdevices)
            self.deviceselector.config(state=DISABLED)
            self.deviceselector.grid(row=3, column=1)
        else:
            self.devicename.set("NO DEVICES AVAILABLE")
            self.recordcam.config(state=DISABLED)
            self.deviceselector = OptionMenu(self, self.devicename,
                                             "NO DEVICES AVAILABLE")
            self.deviceselector.config(state=DISABLED)
            self.deviceselector.grid(row=3, column=1)

        self.opButton = Button(self,
                               text="⚙ Additional Options...",
                               command=self.openSettings)
        self.opButton.grid(row=4, column=1, sticky='e')

        # the "start recording" button
        self.startButton = Button(self,
                                  text="⏺ Start Recording",
                                  command=self.startRecord)
        self.startButton.grid(row=5, column=0, columnspan=2)

        # some variables
        self.recording = False  # are we recording?
        self.proc = None  # the popen object for ffmpeg (during screenrecord)
        self.recorder = recordFile.recorder(
        )  # the "recorder" object for audio (see recordFile.py)
        self.mergeProcess = None  # the popen object for ffmpeg (while merging video and audio files)

        # start the ffmpeg monitoring callback
        self.pollClosed()

    def openSettings(self):
        self.settings = settingsWin(self, self.cmdGen, self.recorder)

    def pollClosed(self):
        """callback that repeats itself every 100ms. Automatically determines if ffmpeg is still running."""
        if self.recording:
            if self.proc.poll() != None:
                self.startRecord()
                messagebox.showerror(
                    "ffmpeg error", "ffmpeg has stopped working. ERROR: \n" +
                    str(self.proc.stderr.read()).replace('\\r\\n', '\n'))
            if self.recorder.error:
                self.startRecord()
        if self.mergeProcess and self.recording == False:
            if self.mergeProcess.poll() != None:
                self.startButton.config(text="⏺ Start Recording", state=NORMAL)
                self.title(string="Screen Recorder")
        self.after(100, self.pollClosed)

    def enDis(self):
        """Called when the "desktop" radio button is pressed"""
        self.entry2.config(state=DISABLED)
        # self.what.set("desktop")

    def enDis1(self):
        """Called when the "window title" radio button is pressed"""
        self.entry2.config(state=NORMAL)
        # self.what.set("title")

    def checkboxChanged(self):
        """Called when the "record webcam" checkbox is checked or unchecked."""
        #self.rcchecked = not self.rcchecked
        if self.rcchecked.get():
            self.deviceselector.config(state=NORMAL)
        else:
            self.deviceselector.config(state=DISABLED)

    def startRecord(self):
        """toggles recording. Will start conversion subprocess on recording completion"""
        if self.recording == False:
            # change the window
            self.title(string="Screen Recorder (Recording...)")
            self.startButton.config(text="⏹️ Stop Recording")
            self.filename = self.entry1.get()

            # disable interface
            self.entry1.config(state=DISABLED)
            self.radio1.config(state=DISABLED)
            self.radio2.config(state=DISABLED)
            self.deviceselector.config(state=DISABLED)
            self.opButton.config(state=DISABLED)
            if self.what.get() == "title":
                self.entry2.config(state=DISABLED)

            # ensure the existence of the "tmp" directory
            try:
                os.mkdir("tmp")
            except FileExistsError:
                pass

            # start screen recording process
            self.recording = True
            startupinfo = subprocess.STARTUPINFO()
            startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
            startupinfo.wShowWindow = subprocess.SW_HIDE

            # self.cmdGen.setFps(60)
            # self.cmdGen.setEncode('nvenc_h264') # CPU: mpeg4 // NVIDIA: h264_nvenc // AMD: no.
            self.cmdGen.setSource(self.what.get() == "title",
                                  self.entry2.get())
            command = self.cmdGen.getCmd("tmp/tmp.mkv")
            self.proc = subprocess.Popen(args=command,
                                         startupinfo=startupinfo,
                                         stderr=subprocess.PIPE)

            # start audio recording
            self.recorder.record("tmp/tmp.wav")

            # start webcam recording, if checked
            self.recordcam.config(state=DISABLED)
            if self.rcchecked.get() and self.webcamdevices:
                self.webcamrecorder.setDevice(str(self.devicename.get()))
                self.webcamrecorder.startCapture("tmp/webcamtmp.mkv")

            # minimize the window to get it out of the way of the recording
            self.iconify()
        elif self.recording == True:
            self.deiconify()
            defaultFile = self.filename

            # re-enable interface
            self.entry1.config(state=NORMAL)
            self.radio1.config(state=NORMAL)
            self.radio2.config(state=NORMAL)
            self.opButton.config(state=NORMAL)
            if self.webcamdevices:
                self.recordcam.config(state=NORMAL)
                if self.rcchecked.get():
                    self.deviceselector.config(state=NORMAL)
            if self.what.get() == "title":
                self.entry2.config(state=NORMAL)

            available = False
            fileNum = 0

            # stop all recording processes
            self.recording = False
            self.proc.terminate()
            self.recorder.stop_recording()
            if self.rcchecked.get() and self.webcamdevices:
                self.webcamrecorder.stopCapture()
            try:
                os.mkdir("ScreenCaptures")
            except FileExistsError:
                pass

            # change the window title and button text to reflect the current process
            self.title(string="Screen Recorder (converting...)")
            self.startButton.config(
                text="converting your previous recording, please wait...",
                state=DISABLED)

            # start the video conversion process
            startupinfo = subprocess.STARTUPINFO()
            startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
            startupinfo.wShowWindow = subprocess.SW_HIDE
            self.cmdGen.config(audList=self.recorder.devices)
            command = self.cmdGen.getCvtCmd("ScreenCaptures/" + self.filename)
            if not self.recorder.error:
                self.mergeProcess = subprocess.Popen(args=command,
                                                     startupinfo=startupinfo)

            # if self.rcchecked.get():
            #     self.mergeProcess = subprocess.Popen(args= ["ffmpeg","-i",'tmp/tmp.mkv','-i','tmp/tmp.wav','-i','tmp/webcamtmp.mkv','-filter_complex','[2:v] scale=640:-1 [inner]; [0:0][inner] overlay=0:0 [out]',"-shortest",'-map','[out]','-y',"ScreenCaptures/"+self.filename])
            # else:
            #     self.mergeProcess = subprocess.Popen(args= ["ffmpeg","-i",'tmp/tmp.mkv','-i','tmp/tmp.wav',"-shortest",'-y',"ScreenCaptures/"+self.filename], startupinfo=startupinfo)

            # change the screen capture name to something that is not taken
            os.chdir("ScreenCaptures")
            while True:
                matches = 0
                for item in os.listdir():
                    if item == defaultFile:
                        matches += 1
                if matches == 0:
                    self.entry1.delete(0, END)
                    self.entry1.insert(END, defaultFile)
                    break
                else:
                    fileNum += 1
                    file = self.filename.split(".")
                    defaultFile = file[0].rstrip("1234567890") + str(
                        fileNum) + "." + file[1]

            os.chdir("../")
class App(Tk):
    def __init__(self, master=None):
        super().__init__(master)
        self.style = ThemedStyle(self)
        self.style.set_theme('elegance')
        self.iconbitmap(r'data\app.ico')
        self.minsize(450, 300)

        self.title('WoT Battle Counter')
        self.menu_bar = Menu(self)
        self.content = Frame(self)
        self.entry = Entry(self.content)
        self.player_list = Listbox(self.content)
        self.count_button = Button(self)
        self.scrollbar = Scrollbar(self.content)
        self.buttons_frame = Frame(self)
        self.sort_button = Checkbutton(self.buttons_frame)
        self.progressbar = Progressbar(self.buttons_frame)
        self.sort_variable = IntVar(self)
        self.PlayerObjects = []
        self.replays = []
        self.player_names = []
        self.offset = 0
        self.skirmish_value = 1
        self.advance_value = 1
        self.clan_war_value = 3

    def create_app(self):
        # Config menu entries and attach them
        self.menu_bar.add_command(label='Config',
                                  command=self.open_config_window)
        self.menu_bar.add_command(label='Open replay files',
                                  command=self.open_skirmish_files)
        self.menu_bar.add_command(label='Open list', command=self.load_list)
        self.menu_bar.add_command(label='Save list', command=self.save_list)
        self.menu_bar.add_command(label='Export to file',
                                  command=self.export_to_file)
        self.menu_bar.add_command(label='About', command=about)
        self.config(menu=self.menu_bar)

        # Config main content window
        self.content.pack(fill='both', expand=1)

        # Config Text Entry + bind enter key
        self.entry.config(exportselection=0)
        self.entry.pack(fill='x')
        self.entry.bind('<Return>', self.add_player)

        # Config Listbox + bind delete key
        self.player_list.config(yscrollcommand=self.scrollbar.set)
        self.scrollbar.config(command=self.player_list.yview,
                              orient='vertical')
        self.scrollbar.pack(side='right', fill='y')
        self.player_list.pack(side='left', fill='both', expand=1)
        self.player_list.bind('<Delete>', self.remove_player)

        # Count button at the bottom
        self.count_button.config(
            text='Count!', command=Thread(target=self.decode_replays).start)
        self.count_button.pack(side='right', padx=10, pady=10)
        self.count_button.state(['disabled'])

        # Config button frame + button + progressbar
        self.buttons_frame.pack(side='left',
                                fill='both',
                                pady=5,
                                padx=5,
                                expand=1)
        self.sort_button.config(text="Sort the list",
                                variable=self.sort_variable)
        self.sort_button.pack(anchor='nw', pady=3, padx=3)
        self.progressbar.config(length=360,
                                mode='indeterminate',
                                orient=HORIZONTAL,
                                maximum=10)
        self.progressbar.pack(anchor='e', pady=3, padx=3)

        # Config the style
        Style().configure('TEntry', background='white')
        Style().configure('TButton', font=('Roboto', 12))
        Style().configure('OK.TButton', font=('Roboto', 12, 'bold'))

        # Loading configuration
        self.load_config()

        # Start the app
        self.mainloop()

    def add_player(self, event):
        name = self.entry.get()
        self.entry.delete(0, 'end')
        player_obj = Player(name)
        self.PlayerObjects.append(player_obj)
        if self.sort_variable.get() == 1:
            self.PlayerObjects.sort(key=lambda player: player.name.lower())
            self.player_list.delete(0, 'end')
            for player in self.PlayerObjects:
                self.player_list.insert('end',
                                        (self.PlayerObjects.index(player) +
                                         self.offset + 1, player.name))
        else:
            self.player_list.delete(0, 'end')
            for player in self.PlayerObjects:
                self.player_list.insert('end',
                                        (self.PlayerObjects.index(player) +
                                         self.offset + 1, player.name))

    def remove_player(self, event):
        select = self.player_list.curselection()
        name = self.player_list.get(select)
        self.player_list.delete(select)
        for player in self.PlayerObjects:
            if name.split()[1] == player.name:
                self.PlayerObjects.remove(player)

        self.player_list.delete(0, 'end')
        for player in self.PlayerObjects:
            self.player_list.insert('end', (self.PlayerObjects.index(player) +
                                            self.offset + 1, player.name))

    def open_skirmish_files(self):
        directory_path = filedialog.askdirectory()
        if not path.exists(directory_path):
            return
        self.replays = self.list_dir(directory_path)
        self.count_button.state(['!disabled'])

    def save_list(self):
        file_path = filedialog.asksaveasfilename(defaultextension='.json')
        if not path.exists(file_path):
            return
        players = list()
        for player in self.PlayerObjects:
            players.append(player.name)
        if path.isfile(file_path):
            f = open(file_path, 'w')
        else:
            f = open(file_path, 'x')
        f.seek(0)
        f.write(dumps(players))

    def load_list(self):
        file_path = filedialog.askopenfilename(
            filetypes=[('json-file', '*.json'), ('all files', '*.*')])
        if path.isfile(file_path):
            self.player_list.delete(0, 'end')
            f = open(file_path, 'r')
            players = loads(f.read())
            for name in players:
                player_obj = Player(name)
                self.PlayerObjects.append(player_obj)
            for player in self.PlayerObjects:
                self.player_list.insert('end',
                                        (self.PlayerObjects.index(player) +
                                         self.offset + 1, player.name))

    def export_to_file(self):
        file_path = filedialog.asksaveasfilename(defaultextension='.txt')
        if path.isfile(file_path):
            f = open(file_path, 'w')
        elif path.exists(file_path):
            f = open(file_path, 'x')
        else:
            return
        data = str()
        for player in self.PlayerObjects:
            if player.battles >= 100:
                data += f'{player.battles}  {player.name} \n'
            elif player.battles >= 10:
                data += f'{player.battles}   {player.name} \n'
            elif player.battles > 0:
                data += f'{player.battles}    {player.name} \n'
        f.seek(0)
        f.write(data)

    def list_dir(self, path):
        entries = listdir(path)
        re_replay = compile('\.wotreplay')
        re_file = compile('\.')
        replays = []
        # recursive function for searching in subdirectories for .wotreplay files and putting them into a list
        for entry in entries:
            if not search(re_file, entry):
                new_path = path + "/" + entry
                new_replays = self.list_dir(new_path)
                for replay in new_replays:
                    replays.append(replay)
            elif search(re_replay, entry):
                replays.append((path + '/' + entry))
            elif not search(re_replay, entry) and search(re_file, entry):
                continue
        return replays

    def decode_replays(self):
        self.progressbar.start()
        thread_queue = Queue()
        replay_list_1 = [
            self.replays[x] for x in range(0, round(len(self.replays) / 4))
        ]
        replay_list_2 = [
            self.replays[x] for x in range(round(len(self.replays) / 4),
                                           round(len(self.replays) / 4) * 2)
        ]
        replay_list_3 = [
            self.replays[x] for x in range(
                round(len(self.replays) / 4) * 2,
                round(len(self.replays) / 4) * 3)
        ]
        replay_list_4 = [
            self.replays[x] for x in range(
                round(len(self.replays) / 4) * 3, len(self.replays))
        ]

        thread_1 = Thread(target=self.convert_binary_data,
                          args=(replay_list_1, thread_queue))
        thread_2 = Thread(target=self.convert_binary_data,
                          args=(replay_list_2, thread_queue))
        thread_3 = Thread(target=self.convert_binary_data,
                          args=(replay_list_3, thread_queue))
        thread_4 = Thread(target=self.convert_binary_data,
                          args=(replay_list_4, thread_queue))

        threads = (thread_1, thread_2, thread_3, thread_4)

        for thread in threads:
            thread.start()

        sleep(1)
        if self.listen_for_result(threads):
            self.player_names = thread_queue.get()
            for name in thread_queue.get():
                self.player_names.append(name)
            for name in thread_queue.get():
                self.player_names.append(name)
            for name in thread_queue.get():
                self.player_names.append(name)

        # COUNTING TIME!
        for name in self.player_names:
            for player in self.PlayerObjects:
                if name[0] == player.name:
                    player.battles += name[1]

        # Insert names together with battle count back into the list
        self.player_list.delete(0, 'end')
        for player in self.PlayerObjects:
            if player.battles > 0:
                self.player_list.insert(
                    'end', (self.PlayerObjects.index(player) + self.offset + 1,
                            player.name, player.battles))
            else:
                continue
        self.progressbar.stop()

    def listen_for_result(self, threads):
        # Check if all replay results have come in
        alive_threads = 0
        for thread in threads:
            thread.join(0.1)
        for thread in threads:
            if thread.is_alive():
                print("thread not ded")
                alive_threads += 1
        if alive_threads > 0:
            if self.listen_for_result(threads):
                return True
        return True

    def convert_binary_data(self, replays, queue):
        player_names = list()
        for replay in range(len(replays)):
            filename_source = replays[replay]
            f = open(filename_source, 'rb')
            f.seek(8)
            size = f.read(4)
            data_block_size = unpack('I', size)[0]
            f.seek(12)
            my_block = f.read(int(data_block_size))

            # Convert binary data into a json and then into an iterable tuple
            json_replay = loads(my_block)
            players_dict = [(v, k)
                            for (k, v) in dict.items(json_replay['vehicles'])]

            # Extract names and append to a list
            for player_id in players_dict:
                player_name = player_id[0]['name']
                if json_replay['battleType'] == 20:
                    player_names.append((player_name, self.skirmish_value))
                elif json_replay['battleType'] == 13:
                    player_names.append((player_name, self.clan_war_value))
                else:
                    player_names.append((player_name, 1))
        queue.put(player_names)

    def open_config_window(self):
        config_window = Toplevel(self)
        config_window.iconbitmap(r'data\app.ico')
        config_window.minsize(500, 350)

        config_frame = Labelframe(config_window)
        config_frame.config(text="App Configuration",
                            relief='groove',
                            borderwidth=5)
        config_frame.pack(expand=1, fill='both', padx=5, pady=5)

        offset_title = Label(config_frame)
        offset_title.config(text='Numbering offset (Default 0)')
        offset_title.pack(anchor='nw', padx=5, pady=5)

        offset_entry = Entry(config_frame)
        offset_entry.config(
            width=10,
            exportselection=0,
            validate='key',
            validatecommand=(offset_entry.register(validate_config_entry),
                             '%P'))
        offset_entry.pack(anchor='nw', padx=5, pady=5)

        battle_value_frame = Labelframe(config_frame)
        battle_value_frame.config(text='Battle weighting',
                                  relief='groove',
                                  borderwidth=5)
        battle_value_frame.pack(anchor='nw',
                                fill='both',
                                expand=1,
                                padx=5,
                                pady=5)

        descriptor_frame = Frame(battle_value_frame)
        descriptor_frame.pack(side='left', fill='both', expand=1)

        entry_frame = Frame(battle_value_frame)
        entry_frame.pack(side='left', fill='both', expand=1)

        skirmish_title = Label(descriptor_frame)
        skirmish_title.config(text='Skirmish weighting (Default = 1):')
        skirmish_title.pack(anchor='nw', padx=5, pady=7)

        skirmish_entry = Entry(entry_frame)
        skirmish_entry.config(
            width=10,
            exportselection=0,
            validate='key',
            validatecommand=(skirmish_entry.register(validate_config_entry),
                             '%P'))
        skirmish_entry.pack(anchor='nw', padx=5, pady=5)

        advance_title = Label(descriptor_frame)
        advance_title.config(text='Advance weighting (Default = 1):')
        advance_title.pack(anchor='nw', padx=5, pady=10)

        advance_entry = Entry(entry_frame)
        advance_entry.config(
            width=10,
            exportselection=0,
            validate='key',
            validatecommand=(advance_entry.register(validate_config_entry),
                             '%P'))
        advance_entry.pack(anchor='nw', padx=5, pady=5)

        clan_war_title = Label(descriptor_frame)
        clan_war_title.config(text='Clan War weighting (Default = 3):')
        clan_war_title.pack(anchor='nw', padx=5, pady=6)

        clan_war_entry = Entry(entry_frame)
        clan_war_entry.config(
            width=10,
            exportselection=0,
            validate='key',
            validatecommand=(clan_war_entry.register(validate_config_entry),
                             '%P'))
        clan_war_entry.pack(anchor='nw', padx=5, pady=5)

        buttons_frame = Frame(config_frame)
        buttons_frame.pack(anchor='sw', fill='both', expand=0)

        apply_button = Button(buttons_frame)
        apply_button.config(text='Apply',
                            command=partial(self.config_apply, offset_entry,
                                            skirmish_entry, advance_entry,
                                            clan_war_entry))
        apply_button.pack(side='right', padx=5, pady=5)

        cancel_button = Button(buttons_frame)
        cancel_button.config(text='Cancel',
                             command=lambda: config_window.destroy())
        cancel_button.pack(side='right', padx=5, pady=5)

        ok_button = Button(buttons_frame)
        ok_button.config(text='OK',
                         style='OK.TButton',
                         command=partial(self.config_ok, offset_entry,
                                         skirmish_entry, advance_entry,
                                         clan_war_entry, config_window))
        ok_button.pack(side='right', padx=5, pady=5)

        offset_entry.insert('end', self.offset)
        skirmish_entry.insert('end', self.skirmish_value)
        advance_entry.insert('end', self.advance_value)
        clan_war_entry.insert('end', self.clan_war_value)

    def config_ok(self, offset, skirmish, advance, clan_war, window):
        self.offset = int(offset.get())
        self.skirmish_value = int(skirmish.get())
        self.advance_value = int(advance.get())
        self.clan_war_value = int(clan_war.get())
        data = {
            'offset': offset.get(),
            'skirmish_value': skirmish.get(),
            'advance_value': advance.get(),
            'clan_war_value': clan_war.get()
        }
        if path.isfile(r'config.json'):
            f = open(r'config.json', 'w')
        else:
            f = open(r'config.json', 'x')
        f.seek(0)
        f.write(dumps(data))
        window.destroy()

    def config_apply(self, offset, skirmish, advance, clan_war):
        self.offset = int(offset.get())
        self.skirmish_value = int(skirmish.get())
        self.advance_value = int(advance.get())
        self.clan_war_value = int(clan_war.get())
        data = {
            'offset': offset.get(),
            'skirmish_value': skirmish.get(),
            'advance_value': advance.get(),
            'clan_war_value': clan_war.get()
        }
        if path.isfile(r'config.json'):
            f = open(r'config.json', 'w')
        else:
            f = open(r'config.json', 'x')
        f.seek(0)
        f.write(dumps(data))

    def load_config(self):
        if path.isfile(r'config.json'):
            f = open(r'config.json', 'r')
            data = loads(f.read())
            print(data)
            self.offset = int(data['offset'])
            self.skirmish_value = int(data['skirmish_value'])
            self.advance_value = int(data['advance_value'])
            self.clan_war_value = int(data['clan_war_value'])
        else:
            pass
Example #7
0
class Application(Frame):
    def __init__(self, master=None):
        super().__init__(master)
        self.master = master
        self.grid(row=0, column=0, sticky="nesw", padx=20, pady=20)
        self._create_widgets()
        self._selected_genre = set()
        self._helper = None
        self._books = []
        self._is_started = False

    def _add_titles_to_listbox(self):
        books = []
        self.books.delete(0, tk.END)
        for genre in sorted(self._selected_genre):
            books.extend(self._helper.get_books_in_genres(self._books, genre))

        books.sort()
        self.books.insert(tk.END, *books)

    def _create_widgets(self):
        def _create_label(frame, text, coordinates):
            label = Label(frame,
                          text=text,
                          justify=tk.LEFT,
                          anchor="w",
                          width=20)
            label.grid(row=coordinates[0], column=coordinates[1])

        _create_label(self, "Output Folder:", (0, 0))
        self.output_path = Entry(self)
        self.output_path.grid(row=1,
                              column=0,
                              columnspan=2,
                              sticky="ew",
                              pady=(0, 15))
        _create_label(self, "Available Genres:", (2, 0))
        _create_label(self, "Books to Download:", (2, 1))
        self.genres = tk.Listbox(self, selectmode="multiple")

        def on_genre_select(evt):
            indices = evt.widget.curselection()
            self._selected_genre.clear()
            for index in indices:
                value = evt.widget.get(index)
                self._selected_genre.add(value)
            self._add_titles_to_listbox()

        self.genres.bind("<<ListboxSelect>>", on_genre_select)
        self.genres["height"] = 20
        self.genres["width"] = 30
        self.genres.grid(row=3, column=0, padx=(0, 10))

        self.books = tk.Listbox(self)
        self.books["height"] = 20
        self.books["width"] = 30
        self.books.grid(row=3, column=1, padx=(10, 0))

        self._pdf = tk.IntVar(value=1)
        self.pdf = Checkbutton(self, text="PDF", variable=self._pdf)
        self.pdf.grid(row=4, column=0, pady=10)

        self._epub = tk.IntVar(value=1)
        self.epub = Checkbutton(self, text="EPUB", variable=self._epub)
        self.epub.grid(row=4, column=1, pady=10)

        self._status_text = tk.StringVar()
        self.status = Label(self, textvariable=self._status_text)
        self.status.grid(row=5, column=0, columnspan=2)
        self._current_bar_value = tk.IntVar(value=0)
        self.bar = Progressbar(self, variable=self._current_bar_value)

        self._download_btn = tk.StringVar()
        db = Button(self, textvariable=self._download_btn)
        db["command"] = self.start_download
        self._download_btn.set("Download")
        db.grid(row=7, column=0, columnspan=2, pady=10)
        LOG.info("All widgets created.")

    def set_output_folder(self, path):
        if self.output_path:
            self.output_path.insert(tk.END, path)

    def populate_genres(self, genres):
        self._genre_mapping = genres
        for genre in sorted(genres):
            self.genres.insert(tk.END, genre)
        LOG.info("Added {} genres".format(len(genres)))

    def start_download(self):
        titles = self.books.get(0, tk.END)
        if not titles:
            self._status_text.set("Please select a genre to continue")
            return

        self._is_started = not self._is_started
        self._toggle_state(not self._is_started)

        if self._is_started:
            books = self._books.copy()
            for book in books.iterrows():
                if book[1]["Book Title"] not in titles:
                    books = books.drop(index=book[0])

            LOG.info("Starting to download {} books".format(len(books)))
            self.bar["maximum"] = len(books)
            self._helper.STOP_FLAG = False
            self._download_thread = threading.Thread(
                daemon=True,
                target=self._helper.download_books,
                args=(books, self.output_path.get()),
                kwargs={
                    "pdf": self._pdf.get(),
                    "epub": self._epub.get(),
                    "verbose": True,
                    "label": self._status_text,
                    "progressbar": self._current_bar_value,
                },
            )
            self._download_thread.start()
            self._thread_check_in(self._download_thread)
        else:
            self._helper.STOP_FLAG = True

    def _thread_check_in(self, thread, period=100):
        if thread.isAlive():
            self.master.after(period, self._thread_check_in, thread, period)
        else:
            self._is_started = False
            self._toggle_state(True)

    def _toggle_state(self, enable):
        state = tk.NORMAL if enable else tk.DISABLED
        text = "Download" if enable else "Stop"
        status = "" if enable else "Starting downloader ..."
        self.output_path.config(state=state)
        self.genres.config(state=state)
        self.books.config(state=state)
        self.pdf.config(state=state)
        self.epub.config(state=state)
        self._download_btn.set(text)
        self._status_text.set(status)

        if enable:
            self.bar.grid_remove()
        else:
            self.bar.grid(row=6, column=0, columnspan=2, pady=10, sticky="ew")
Example #8
0
class SettingsEditor(Frame):
    def __init__(self, master, app_main, **kwargs):
        super().__init__(master, **kwargs)

        self.app = app_main
        self.create_settings_ui()

    def create_settings_ui(self):
        self.rowconfigure(0, weight=1)
        self.columnconfigure(0, weight=1)
        self.frm_settings = Frame(self)
        # self.frm_settings.rowconfigure(2, weight=1)
        # self.frm_settings.columnconfigure(0, weight=1)

        is_valid_req_delay = self.register(functools.partial(_is_number,
                                                             min=0))
        is_valid_duration = self.register(
            functools.partial(_is_number, min=0, max=20))
        is_valid_history_retention = self.register(
            functools.partial(_is_number, min=0, max=100))
        is_valid_max_conns = self.register(
            functools.partial(_is_number, min=1, max=20, integer=True))
        is_valid_num_workers = self.register(
            functools.partial(_is_number,
                              min=0,
                              max=os.cpu_count() or 8,
                              integer=True))

        self.frm_settings.grid(padx=10, pady=10, sticky='nsew')

        frm_basic = LabelFrame(self.frm_settings, text='Basic')
        frm_basic.grid(padx=5, pady=5, sticky='nsew', row=0, column=0, ipadx=5)

        lbl = Label(frm_basic, text='League:')
        lbl.grid(row=0, column=0, padx=5, pady=3, sticky='w')
        self.cmb_league = Combobox(frm_basic,
                                   state=READONLY,
                                   values=leagueOptions)
        self.cmb_league.grid(row=0, column=1, pady=3, sticky='nsew')
        # lbl = Label(frm_basic, text='Minimum request delay(s):')
        # lbl.grid(row=1, column=0, padx=5, pady=3, sticky='w')
        # self.entry_req_delay = Entry(frm_basic, validate='all', validatecommand=(is_valid_req_delay, '%P'))
        # self.entry_req_delay.grid(row=1, column=1, pady=3, sticky='nsew')
        # lbl = Label(frm_basic, text='Scan mode:')
        # lbl.grid(row=2, column=0, padx=5, pady=3, sticky='w')
        # self.cmb_scan_mode = Combobox(frm_basic, state=READONLY, values=scanModeOptions)
        # self.cmb_scan_mode.grid(row=2, column=1, pady=3, sticky='nsew')
        lbl = Label(frm_basic, text='Notification duration(s):')
        lbl.grid(row=3, column=0, padx=5, pady=3, sticky='w')

        self.entry_notification_duration = Entry(
            frm_basic,
            validate='all',
            validatecommand=(is_valid_duration, '%P'))
        self.entry_notification_duration.grid(row=3,
                                              column=1,
                                              pady=3,
                                              sticky='nsew')

        frm = LabelFrame(self.frm_settings, text='Advanced')
        frm.grid(pady=5, sticky='nsew', row=0, column=1, ipadx=5)

        lbl = Label(frm, text='Scan mode:')
        lbl.grid(row=0, column=0, padx=5, pady=3, sticky='w')
        self.cmb_scan_mode = Combobox(frm,
                                      state=READONLY,
                                      values=scanModeOptions)
        self.cmb_scan_mode.grid(row=0, column=1, pady=3, sticky='nsew')

        lbl = Label(frm, text='Min. request delay:')
        lbl.grid(row=1, column=0, padx=5, pady=3, sticky='w')
        self.entry_req_delay = Entry(frm,
                                     validate='all',
                                     validatecommand=(is_valid_req_delay,
                                                      '%P'))
        self.entry_req_delay.grid(row=1, column=1, pady=3, sticky='nsew')
        lbl = Label(frm, text='(seconds)')
        lbl.grid(row=1, column=2, padx=(5, 0), pady=3, sticky='w')

        lbl = Label(frm, text='Max connections:')
        lbl.grid(row=2, column=0, padx=5, pady=3, sticky='w')
        self.entry_max_conns = Entry(frm,
                                     validate='all',
                                     validatecommand=(is_valid_max_conns,
                                                      '%P'))
        self.entry_max_conns.grid(row=2, column=1, pady=3, sticky='nsew')

        lbl = Label(frm, text='Parsers #:')
        lbl.grid(row=3, column=0, padx=5, pady=3, sticky='w')
        self.entry_num_workers = Entry(frm,
                                       validate='all',
                                       validatecommand=(is_valid_num_workers,
                                                        '%P'))
        self.entry_num_workers.grid(row=3, column=1, pady=3, sticky='nsew')
        lbl = Label(frm, text='(0 = Auto)')
        lbl.grid(row=3, column=2, padx=(5, 0), pady=3, sticky='w')

        lbl = Label(frm, text='History retention:')
        lbl.grid(row=4, column=0, padx=5, pady=3, sticky='w')
        self.entry_history_retention = Entry(
            frm,
            validate='all',
            validatecommand=(is_valid_history_retention, '%P'))
        self.entry_history_retention.grid(row=4,
                                          column=1,
                                          pady=3,
                                          sticky='nsew')
        lbl = Label(frm, text='(days)')
        lbl.grid(row=4, column=2, padx=(5, 0), pady=3, sticky='w')

        frm = Frame(frm_basic)
        frm.grid(row=4, column=0)

        self.var_notify = BooleanVar()
        self.var_notify.trace_variable(
            'w', lambda a, b, c: self._on_notify_option_change())
        self.cb_notifications = Checkbutton(frm,
                                            text='Growl notifications',
                                            variable=self.var_notify)
        self.cb_notifications.grid(row=0, column=0, padx=5, pady=3, sticky='w')

        self.var_notify_copy = BooleanVar()
        self.cb_notify_copy = Checkbutton(frm,
                                          text='Copy message',
                                          variable=self.var_notify_copy)
        self.cb_notify_copy.grid(row=1, column=0, padx=5, pady=3, sticky='w')

        self.var_notify_play_sound = BooleanVar()
        self.cb_notify_play_sound = Checkbutton(
            frm, text='Play sound', variable=self.var_notify_play_sound)
        self.cb_notify_play_sound.grid(row=2,
                                       column=0,
                                       padx=5,
                                       pady=3,
                                       sticky='w')

        frm_btns = Frame(self.frm_settings)
        frm_btns.grid(row=2, columnspan=3, pady=(20, 5), sticky='w')

        self.btn_apply = Button(frm_btns,
                                text='Apply',
                                command=self.applyChanges)
        self.btn_apply.grid(row=0, column=0, padx=5)
        self.btn_reload = Button(frm_btns,
                                 text='Reload',
                                 command=self.loadSettings)
        self.btn_reload.grid(row=0, column=1)

    def _on_notify_option_change(self):
        state = NORMAL if self.var_notify.get() else DISABLED
        self.cb_notify_copy.config(state=state)
        self.cb_notify_play_sound.config(state=state)

    def applyChanges(self):
        cfg = AppConfiguration()

        cfg.league = self.cmb_league.get() or leagueOptions[0]
        cfg.notify = self.var_notify.get()
        cfg.notify_copy_msg = self.var_notify_copy.get()
        cfg.notify_play_sound = self.var_notify_play_sound.get()
        cfg.notification_duration = float(
            self.entry_notification_duration.get() or 4)
        cfg.request_delay = float(self.entry_req_delay.get() or 0.7)
        cfg.scan_mode = self.cmb_scan_mode.get() or scanModeOptions[0]

        cfg.history_retention = int(self.entry_history_retention.get() or 1)
        cfg.max_conns = int(self.entry_max_conns.get() or 8)
        cfg.num_workers = int(self.entry_num_workers.get() or 0)
        cfg.smooth_delay = config.smooth_delay

        self.app.update_configuration(cfg)

    def loadSettings(self):
        self.cmb_league.set(config.league)
        self.cmb_scan_mode.set(config.scan_mode)
        self.entry_notification_duration.delete(0, END)
        self.entry_notification_duration.insert(0,
                                                config.notification_duration)
        self.var_notify.set(config.notify)
        self.var_notify_copy.set(config.notify_copy_msg)
        self.var_notify_play_sound.set(config.notify_play_sound)
        self.entry_req_delay.delete(0, END)
        self.entry_req_delay.insert(0, config.request_delay)

        self.entry_history_retention.delete(0, END)
        self.entry_history_retention.insert(0, config.history_retention)
        self.entry_max_conns.delete(0, END)
        self.entry_max_conns.insert(0, config.max_conns)
        self.entry_num_workers.delete(0, END)
        self.entry_num_workers.insert(0, config.num_workers)
Example #9
0
class TkApp(Tk):
    """
    The main Tk class for the gui of simplebackup
    """
    def __init__(self, **kwargs):
        super().__init__()
        title = "Simple Backup | V" + __version__
        self.wm_title(title)
        self.protocol("WM_DELETE_WINDOW", self.on_closing)

        self.__thread = None
        self.__files_found = 0
        self.__files_copied = 0

        config_fn = kwargs.get("config_fn", user_config_filepath())
        self.__app_config = Config_Handler(config_fn)
        self.__curr_config = self.__app_config.default_config_i

        self.__menu = Menu(self)
        self.__menu_file = Menu(self.__menu, tearoff=0)
        self.__menu_file.add_command(label="Quit", command=self.quit)
        self.__menu_config = Menu(self.__menu, tearoff=0)
        self.__menu_config.add_command(label="New", command=self.new_config)
        self.__menu_config.add_command(label="Load",
                                       command=self.switch_config)
        self.__menu_config.add_command(label="Change Default",
                                       command=self.change_default_config)
        self.__menu_config.add_command(label="Rename Current",
                                       command=self.rename_curr_conf)
        self.__menu_config.add_separator()
        self.__menu_config.add_command(label="Delete Current",
                                       command=self.delete_current_config)
        self.__menu_config.add_command(label="Delete All",
                                       command=self.reset_config)
        self.__menu_help = Menu(self.__menu, tearoff=0)
        self.__menu_help.add_command(label="Check for Updates",
                                     command=self.show_update_popup)
        self.__menu_help.add_command(label="About",
                                     command=self.show_about_popup)
        self.__menu.add_cascade(label="File", menu=self.__menu_file)
        self.__menu.add_cascade(label="Config", menu=self.__menu_config)
        self.__menu.add_cascade(label="Help", menu=self.__menu_help)

        self.__title_l = Label(self, text=title, font=(16))
        self.__curr_config_name_l = Label(self)
        self.__last_backup_l = Label(self)
        self.__set_versions_to_keep = Button(
            self,
            text="Set Versions To Keep",
            command=self.update_versions_to_keep)
        self.__versions_to_keep_l = Label(self)
        self.__inc_folder_bnt = Button(self,
                                       text="Include Another Folder",
                                       command=self.add_included_folder)
        self.__included_folders_lb = Listbox(self, height=4)
        self.__included_folders_lb.bind("<<ListboxSelect>>",
                                        self.remove_selected_included_folder)
        self.__included_folders_lb.bind('<FocusOut>',
                                        self.deselect_included_folder)
        self.__excl_folder_bnt = Button(self,
                                        text="Exclude Another Folder",
                                        command=self.add_excluded_folder)
        self.__excluded_folders_lb = Listbox(self, height=4)
        self.__excluded_folders_lb.bind("<<ListboxSelect>>",
                                        self.remove_selected_excluded_folder)
        self.__excluded_folders_lb.bind('<FocusOut>',
                                        self.deselect_excluded_folder)
        self.__backup_to_bnt = Button(self,
                                      text="Backup Folder",
                                      command=self.set_backup_folder)
        self.__backup_folder_l = Label(self)
        self.__use_tar_l = Label(self, text="Use Tar")
        self.__use_tar_var = BooleanVar(self)
        self.__use_tar_var.trace_add("write", self.use_tar_changed)
        self.__use_tar = Checkbutton(self, variable=self.__use_tar_var)
        self.__backup_start_bnt = Button(self,
                                         text="Start Backup",
                                         command=self.start_backup)
        self.__progress = Progressbar(self)
        self.__statusbar = Label(self, text="ok", relief=SUNKEN, anchor=W)
        self._load_display()
        self._layout()

        if self.__app_config.show_help:
            self.show_help_popup()

    def on_closing(self):
        """
        called on window close
        """
        if self.__files_found != self.__files_copied:
            if messagebox.askyesno("Backup Running",
                                   "Do you want to stop the backup?"):
                self.destroy()
        else:
            self.destroy()

    def _load_display(self):
        """
        load the widgets with data from the current backup config,
        should be run after loading a config from file and at app launch
        """
        self.__versions_to_keep = self.__app_config.get_versions_to_keep(
            self.__curr_config)
        self.__included_folders = self.__app_config.get_included_folders(
            self.__curr_config)
        self.__excluded_folders = self.__app_config.get_excluded_folders(
            self.__curr_config)
        self.__backup_location = self.__app_config.get_backup_path(
            self.__curr_config)

        curr_conf_name = self.__app_config.get_config_name(self.__curr_config)
        self.__curr_config_name_l.config(text=f"Config Name: {curr_conf_name}")
        self.__last_backup_l.config(
            text=
            f"Last Known Backup: {self.__app_config.get_human_last_backup(self.__curr_config)}"
        )
        self.__versions_to_keep_l.config(text=self.__versions_to_keep)
        self.__included_folders_lb.delete(0, END)
        self.__included_folders_lb.insert(0, *self.__included_folders)
        self.__excluded_folders_lb.delete(0, END)
        self.__excluded_folders_lb.insert(0, *self.__excluded_folders)
        self.__backup_folder_l.config(text=str(self.__backup_location))
        self.__use_tar_var.set(
            self.__app_config.get_use_tar(self.__curr_config))

    def switch_config(self):
        """
        switches what config to use for backup,
        asks the user for a config to load,
        then loads the display
        """
        next_combo = ask_combobox("Load Config", "Config Name",
                                  self.__app_config.get_config_names())
        if next_combo != None:
            self.__curr_config = next_combo
            self._load_display()

    def change_default_config(self):
        """
        switches what config to use for the default backup,
        asks the user for a config to load
        """
        next_combo = ask_combobox("Default Config", "Config Name",
                                  self.__app_config.get_config_names())
        if next_combo != None:
            self.__app_config.default_config_i = next_combo

    def rename_curr_conf(self):
        """
        rename a existing config,
        will ask the user in a popup string input
        """
        new_name = simpledialog.askstring("Rename Config", "New Name")
        if new_name:
            self.__app_config.rename_config(self.__curr_config, new_name)
            self._load_display()

    def new_config(self):
        """
        creates a new empty backup config,
        asks the user for config name
        """
        name = simpledialog.askstring("New Config", "Config Name")
        if name:
            self.__app_config.create_config(name)

    def delete_current_config(self):
        """
        deletes the current selected config, asks the user to confirm
        """
        if messagebox.askyesno(
                "Confirm Delete",
                "Are you sure you want to delete the current config?"):
            self.__app_config.remove_config(self.__curr_config)
            self.__curr_config = self.__app_config.default_config_i
            self._load_display()

    def reset_config(self):
        """
        resets all the user configs, asks the user to confirm
        """
        if messagebox.askyesno(
                "Confirm Reset",
                "Are you sure you want to reset the all configurations?"):
            self.__app_config.reset_config()
            self.__curr_config = self.__app_config.default_config_i
            self._load_display()

    def use_tar_changed(self, *args):
        """
        called each time the __use_tar_var is called
        """
        self.__app_config.set_use_tar(self.__curr_config,
                                      self.__use_tar_var.get())

    def update_versions_to_keep(self):
        """
        update the number of versions to keep,
        asks the user for a integer
        """
        new_val = simpledialog.askinteger(
            "Versions To Keep",
            "How many backups do you want to keep",
            minvalue=0)
        if new_val != self.__versions_to_keep and new_val != None:
            self.__versions_to_keep = new_val
            self.__app_config.set_versions_to_keep(self.__curr_config,
                                                   self.__versions_to_keep)
            self.__versions_to_keep_l.config(text=self.__versions_to_keep)

    def deselect_included_folder(self, *args):
        """
        deselects the selected element in included folder
        """
        self.__included_folders_lb.selection_clear(0, END)

    def deselect_excluded_folder(self, *args):
        """
        deselects the selected element in excluded folder
        """
        self.__excluded_folders_lb.selection_clear(0, END)

    def add_included_folder(self):
        """
        add a folder to include in the backup,
        will ask user for a directory
        """
        folder = filedialog.askdirectory(initialdir="/",
                                         title="Select Folder To Backup")
        if folder:
            folder_path = Path(folder)
            if folder_path != self.__backup_location:
                self.__included_folders.append(folder_path)
                self.__included_folders_lb.insert(END, folder_path)
                self.__app_config.set_included_folders(self.__curr_config,
                                                       self.__included_folders)
            else:
                messagebox.showwarning(
                    title="Folder Same As Backup Path",
                    message=
                    "You selected a folder that was the same as the backup path!"
                )

    def remove_selected_included_folder(self, *args):
        """
        remove the currently selected
        item in the included folders ListBox,
        will ask the user to confirm
        """
        curr_selection = self.__included_folders_lb.curselection()
        # check if there is a selection
        if curr_selection:
            if messagebox.askyesno("Confirm Delete",
                                   "Are you want to delete this folder?"):
                index_to_del = curr_selection[0]
                self.__included_folders.pop(index_to_del)
                self.__app_config.set_included_folders(self.__curr_config,
                                                       self.__included_folders)
                self.__included_folders_lb.delete(index_to_del)
            self.deselect_included_folder()

    def add_excluded_folder(self):
        """
        add a folder to exclude in the backup,
        will ask user for a directory
        """
        folder = filedialog.askdirectory(initialdir="/",
                                         title="Select Folder To Exclude")
        if folder:
            folder_path = Path(folder)
            self.__excluded_folders.append(folder_path)
            self.__excluded_folders_lb.insert(END, folder_path)
            self.__app_config.set_excluded_folders(self.__curr_config,
                                                   self.__excluded_folders)

    def remove_selected_excluded_folder(self, *args):
        """
        remove the currently selected
        item in the excluded folders ListBox,
        will ask the user to confirm
        """

        curr_selection = self.__excluded_folders_lb.curselection()
        # check if there is a selection
        if curr_selection:
            if messagebox.askyesno("Confirm Delete",
                                   "Are you want to delete this folder?"):
                index_to_del = curr_selection[0]
                self.__excluded_folders.pop(index_to_del)
                self.__app_config.set_excluded_folders(self.__curr_config,
                                                       self.__excluded_folders)
                self.__excluded_folders_lb.delete(index_to_del)
            self.deselect_excluded_folder()

    def set_backup_folder(self):
        """
        sets the backup folder by asking the user for a base directory
        """
        folder = filedialog.askdirectory(initialdir="/",
                                         title="Select Where To Backup To")
        if folder:
            self.__backup_location = Path(folder)
            self.__backup_folder_l.config(text=folder)
            self.__app_config.set_backup_path(self.__curr_config,
                                              self.__backup_location)

    def enable_gui(self):
        """
        enable the gui buttons, run when a backup has completed
        """
        self.__set_versions_to_keep.config(state=NORMAL)
        self.__inc_folder_bnt.config(state=NORMAL)
        self.__included_folders_lb.config(state=NORMAL)
        self.__excl_folder_bnt.config(state=NORMAL)
        self.__excluded_folders_lb.config(state=NORMAL)
        self.__backup_to_bnt.config(state=NORMAL)
        self.__use_tar.config(state=NORMAL)
        self.__backup_start_bnt.config(state=NORMAL)

    def disable_gui(self):
        """
        disable the gui buttons, run when a backup is started
        """
        self.__set_versions_to_keep.config(state=DISABLED)
        self.__inc_folder_bnt.config(state=DISABLED)
        self.__included_folders_lb.config(state=DISABLED)
        self.__excl_folder_bnt.config(state=DISABLED)
        self.__excluded_folders_lb.config(state=DISABLED)
        self.__backup_to_bnt.config(state=DISABLED)
        self.__use_tar.config(state=DISABLED)
        self.__backup_start_bnt.config(state=DISABLED)

    def progress_find_incr(self, finished=False):
        """
        increment the progress bar for finding
        files by 1 or mark as finished

            :param finished: mark the progressbar as finished
        """
        if finished:
            self.__progress.config(mode="determinate")
            self.__progress.config(value=0, maximum=self.__files_found)
            self.__statusbar.config(text=f"Found {self.__files_found} Files")
        else:
            self.__files_found += 1
            self.__progress.config(value=self.__files_found)
            self.__statusbar.config(
                text=f"Searching For Files, Found {self.__files_found} Files")

    def progress_copy_incr(self):
        """
        increment the progress bar for copying
        files by 1 or mark as finished
        """
        self.__files_copied += 1
        self.__progress.config(value=self.__files_copied)
        self.__statusbar.config(
            text=f"Copying Files {self.__files_copied} of {self.__files_found}"
        )
        if self.__files_copied == self.__files_found:
            self.__app_config.set_last_backup(self.__curr_config,
                                              datetime.utcnow())
            self.__last_backup_l.config(
                text=
                f"Last Known Backup: {self.__app_config.get_human_last_backup(self.__curr_config)}"
            )
            self.__statusbar.config(text=f"Finished Copying Files")
            messagebox.showinfo(title="Finished Copying Files",
                                message="Finished copying all found files")
            # reset counters
            self.__files_found = 0
            self.__files_copied = 0
            self.__progress.config(value=0, maximum=100)
            self.enable_gui()

    def start_backup(self):
        """
        starts the backup
        """
        if not self.__backup_location:
            # no backup location was selected
            messagebox.showwarning(
                title="Backup Location Not Selected",
                message="You did not select a backup location!")
        elif not self.__included_folders:
            # no folders where found to backup
            messagebox.showwarning(
                title="No Folders To Backup",
                message="You did not add any folders to backup!")
        else:
            # basic checks passed
            self.disable_gui()
            # prep for search of files
            self.__progress.config(mode="indeterminate")
            self.__statusbar.config(text=f"Searching For Files")

            self.__thread = BackupThread(
                self.__included_folders, self.__excluded_folders,
                self.__backup_location, self.__versions_to_keep,
                self.progress_find_incr, self.progress_copy_incr,
                self.handle_error_message, self.__use_tar_var.get())
            # start the background backup thread so GUI wont appear frozen
            self.__thread.start()

    def show_about_popup(self):
        """
        show the about popup
        """
        messagebox.showinfo(
            "About", "simplebackup V" + __version__ +
            """ is cross-platform backup program written in python.
This app was made by enchant97/Leo Spratt.
It is licenced under GPL-3.0""")

    def show_update_popup(self):
        """
        open the default webbrowser to the update url
        """
        webbrowser.open(UPDATE_URL)

    def show_help_popup(self):
        messagebox.showinfo(
            "Welcome",
            """Welcome to simplebackup, here is some help to get you started:
\nIncluding a folder to backup
    - Press the 'Include Folder' button to add a folder to backup
    - Remove a entry by clicking on the list below
\nExcluding a folder from the backup
    - Press the 'Exclude Folder' button to skip a folder to backup
    - Remove a entry by clicking on the list below
\nSetting where backups are stored
    - Click the 'Backup Folder' button to set where backups should be placed
\nMultiple backup configs
    Use the 'Config' button in the titlebar to change varius settings like creating a new config
\nVersions to keep
    This will be the number of backup to keep in the backup folder
""")
        self.__app_config.show_help = False

    def handle_error_message(self, error_type: ERROR_TYPES):
        self.__statusbar.config(text="Failed")
        if error_type is ERROR_TYPES.NO_BACKUP_WRITE_PERMISION:
            messagebox.showerror("No Write Permission",
                                 ERROR_TYPES.NO_BACKUP_WRITE_PERMISION.value)
        elif error_type is ERROR_TYPES.NO_BACKUP_READ_PERMISION:
            messagebox.showerror("No Read Permission",
                                 ERROR_TYPES.NO_BACKUP_READ_PERMISION.value)
        elif error_type is ERROR_TYPES.NO_FILES_FOUND_TO_BACKUP:
            messagebox.showerror("No Files Found",
                                 ERROR_TYPES.NO_FILES_FOUND_TO_BACKUP.value)
        elif error_type is ERROR_TYPES.NO_BACKUP_PATH_FOUND:
            messagebox.showerror("No Backup Path Found",
                                 ERROR_TYPES.NO_BACKUP_PATH_FOUND.value)
        self.__progress.config(mode="determinate")
        self.enable_gui()

    def _layout(self):
        self.config(menu=self.__menu)
        self.__title_l.pack(fill=X, pady=10, padx=5)
        self.__curr_config_name_l.pack(fill=X, padx=5)
        self.__last_backup_l.pack(fill=X, padx=5)
        self.__set_versions_to_keep.pack(fill=X, padx=5)
        self.__versions_to_keep_l.pack(fill=X, padx=5)
        self.__inc_folder_bnt.pack(fill=X, padx=5)
        self.__included_folders_lb.pack(fill=X, padx=5)
        self.__excl_folder_bnt.pack(fill=X, padx=5)
        self.__excluded_folders_lb.pack(fill=X, padx=5)
        self.__backup_to_bnt.pack(fill=X, padx=5)
        self.__backup_folder_l.pack(fill=X, padx=5)
        self.__use_tar_l.pack(fill=X, padx=5)
        self.__use_tar.pack(fill=X, padx=5)
        self.__backup_start_bnt.pack(fill=X, padx=5)
        self.__progress.pack(fill=X)
        self.__statusbar.pack(side=BOTTOM, fill=X)
        self.wm_minsize(300, self.winfo_height())
        self.wm_resizable(True, False)
class DownloadPopup(Toplevel):
    def __init__(self, master, info: dict, message_store: MessageStore, video_id: str = None):
        Toplevel.__init__(self, master)
        self.bind('<Escape>', lambda _: self.cancel())
        self.bind('<Return>', lambda _: self.ok())
        self.title('Get VOD')
        self.transient(master)
        self.grab_set()

        self.info: dict = info
        self.message_store: MessageStore = message_store
        self.chat_downloader: ChatDownloader = None

        self.updated_info: bool = False
        self.status_var = StringVar(value='...')
        self.content = Frame(self)
        self.content.pack(padx=20, pady=15)
        self.video_title_var = StringVar(value='')
        self.download_info_var = StringVar(value='')
        self.eta_var = StringVar(value='')
        Label(self.content, text='Enter a VOD URL or video ID:').pack(side=TOP, anchor=W, pady=(0, 5))
        self.entry = Entry(self.content, width=50)
        self.entry.pack(side=TOP, padx=2, pady=(0, 5))
        Label(self.content, textvariable=self.status_var).pack(side=TOP, anchor=W, pady=(0, 5))

        self.progress_var = IntVar(value=0)
        self.progress = Progressbar(self.content, variable=self.progress_var, maximum=1)
        self.progress.pack(side=TOP, fill=X, padx=2)

        Label(self.content, textvariable=self.video_title_var).pack(side=TOP, anchor=W, pady=(0, 5))
        Label(self.content, textvariable=self.download_info_var).pack(side=TOP, anchor=W, pady=(0, 5))
        Label(self.content, textvariable=self.eta_var).pack(side=TOP, anchor=W, pady=(0, 5))

        self.overwrite_cache_var = BooleanVar(value=False)
        self.overwrite_cache_check = Checkbutton(self.content, text='Overwrite cache',
                                                 variable=self.overwrite_cache_var)
        self.overwrite_cache_check.pack(side=TOP, anchor=W, pady=(0, 5))

        self.button = Button(self.content, text='OK', command=self.ok)
        self.button.pack(side=TOP)
        self.update()
        x_coord = self.master.winfo_x() + (self.master.winfo_width() // 2) - (self.winfo_width() // 2)
        y_coord = self.master.winfo_y() + (self.master.winfo_height() // 2) - (self.winfo_height() // 2)
        self.geometry(f'{self.winfo_width()}x{self.winfo_height()}+{x_coord}+{y_coord}')
        self.entry.focus_set()
        self.protocol('WM_DELETE_WINDOW', self.cancel)

        if video_id:
            self.entry.insert(0, video_id)
            self.overwrite_cache_check.focus_set()

            chat_filename: str = os.path.join(CACHE_FOLDER, f'chat-{video_id}.json')
            if not os.path.exists(chat_filename):
                self.ok()

    def cancel(self):
        if self.chat_downloader:
            self.chat_downloader.kill()
        self.info.clear()
        self.info.update({'title': 'Chat Player'})
        self.destroy()

    def ok(self):
        self.button.config(state=DISABLED)
        self.overwrite_cache_check.config(state=DISABLED)
        self.status_var.set('Validating...')
        self.after(1, self.validate)

    def validate(self):
        video_id: str = self.entry.get()
        if 'http' in video_id or 'twitch.tv' in video_id:
            video_id = parse_url(video_id)
        if len(video_id) > 0 and video_exists(video_id):
            self.chat_downloader = ChatDownloader(video_id, overwrite_cache=self.overwrite_cache_var.get())
            self.chat_downloader.start()
            self.after(1, self.download)
        else:
            self.status_var.set('Error: Invalid URL or video ID.')
            self.button.config(state=NORMAL)

    def download(self):
        if not self.chat_downloader.info:
            self.status_var.set('Getting info')
            self.after(100, self.download)
        elif not self.chat_downloader.messages:
            if not self.updated_info:
                self.status_var.set('Downloading chat')
                self.info.update(self.chat_downloader.info)
                self.video_title_var.set(self.info.get('title'))
                self.updated_info = True
            self.progress_var.set(self.chat_downloader.progress)
            self.download_info_var.set(
                f'{self.chat_downloader.num_messages} messages downloaded. '
                f'Duration {self.chat_downloader.duration_done_str}/{self.chat_downloader.duration_str}.')
            self.eta_var.set(f'ETA: {self.chat_downloader.eta_str}')
            self.after(100, self.download)
        else:
            self.message_store.set_messages(self.chat_downloader.messages)
            self.destroy()
class Application(Frame):
    """Main class, handles GUI and starts logic"""
    def __init__(self, master=None, arguments=()):
        super().__init__(master)

        self._file_path = ""
        self._output_dir = ""
        self._same_dir = IntVar(value=1)        # By default output path is the same as file path
        self._show_password = False
        self._db_type = IntVar(value=self.DB_TYPE_MARIADB())

        self._master = master
        self.grid()
        try:
            self._create_widgets()
        except Exception as ex:
            messagebox.showerror("Error", ex.args[0])

        self._same_dir_as_file()        # Disable output button and it sets output dir at start
        self._convert_progressbar.grid_remove()        # Hide loading bar at start

        if self._is_file_path_in_arguments(arguments):
            self._set_file_path(arguments[1])

    def _is_file_path_in_arguments(self, arguments):
        """Method to check if arguments[1] (because arguments[0] is application
        path) is a file path
        """
        if arguments.__len__() > 1:
            file_path = arguments[1]
            if path.isfile(file_path):
                extension = path.splitext(file_path)[1]
                if extension == ".mdb" or extension == ".accdb":
                    return True
        return False

    def _create_widgets(self):
        """GUI building"""
        # File widgets
        self._filename_label = Label(self._master, width="22", anchor="e", text="Access File (*.mdb, *.accdb):")
        self._filename_label.grid(row=0, column=0)

        self._filename_path_label = Label(self._master, width="50", anchor="w", textvariable=self._file_path, bg="#cccccc")
        self._filename_path_label.grid(row=0, column=1)

        self._browse_file_button = Button(self._master, text="...", width="3", command=self._browse_file)
        self._browse_file_button.grid(row=0, column=2)

        # Password widget
        self._password_label = Label(self._master, width="22", anchor="e", text="Password (else leave empty):")
        self._password_label.grid(row=1, column=0)

        self._password_entry = Entry(self._master, width="58", show="*")
        self._password_entry.grid(row=1, column=1)

        self._password_show_image = PhotoImage(file=path.join(module_dir, "images\\watch_pwd.png")).subsample(8, 8)
        self._password_show_button = Button(self._master, width="3", command=self._show_hide_password, image=self._password_show_image)
        self._password_show_button.grid(row=1, column=2)

        # Checkbox widget
        self._same_dir_as_file_checkbox = Checkbutton(self._master, width="50", text="Same directory as source file", var=self._same_dir, command=self._same_dir_as_file)
        self._same_dir_as_file_checkbox.grid(row=2, column=1, pady=8)

        # Output widgets
        self._output_label = Label(self._master, width="22", anchor="e", text="Output directory:")
        self._output_label.grid(row=3, column=0)

        self._output_dir_label = Label(self._master, width="50", anchor="w", textvariable=self._file_path,
                                       bg="#cccccc")
        self._output_dir_label.grid(row=3, column=1)

        self._browse_dir_button = Button(self._master, text="...", width="3", command=self._browse_dir)
        self._browse_dir_button.grid(row=3, column=2)

        # Radio buttons for PostgreSQL or MySQL/MariaDB
        self._db_type_label = Label(self._master, width="22", anchor="e", text="Database type:")
        self._db_type_label.grid(row=4, column=0)

        self._db_type_frame = Frame(self._master)
        self._db_type_frame.grid(row=4, column=1, columnspan=2, pady=5)

        self._radio_button_postgres = Radiobutton(self._db_type_frame, text="PostgreSQL", var=self._db_type, value=self.DB_TYPE_POSTGRESQL(), width="13")
        self._radio_button_postgres.grid(row=0, column=0)

        self._radio_button_mariadb = Radiobutton(self._db_type_frame, text="MariaDB", var=self._db_type, value=self.DB_TYPE_MARIADB(), width="13")
        self._radio_button_mariadb.grid(row=0, column=1)

        self._radio_button_mysql = Radiobutton(self._db_type_frame, text="MySQL", var=self._db_type, value=self.DB_TYPE_MYSQL(), width="13")
        self._radio_button_mysql.grid(row=0, column=2)

        # Convert widget & progressbar
        self._convert_frame = Frame(self._master)
        self._convert_frame.grid(row=5, column=0, columnspan=2, pady=5)

        self._convert_button = Button(self._convert_frame, width="84", text="CREATE SQL FILE", command=self.convertSQL, state="disabled")
        self._convert_button.grid(row=0, column=0)

        self._convert_progressbar = Progressbar(self._convert_frame, length="512")
        self._convert_progressbar.grid(row=1, column=0)

    def convertSQL(self):
        """SQL file generator"""
        self._convert_progressbar.grid(row=1, column=0)        # Show progressbar
        self._convert_progressbar["value"] = 0
        self._master.config(cursor="wait")
        accessconnector = Accessconnector()
        try:
            driver = accessconnector.driver(self._file_path)
            con = accessconnector.con(driver, self._file_path, self._get_password())
            self._convert_progressbar["value"] = 33
        except (AccessConnectionError, ODBCDriverNotFoundError) as ex:
            self._master.config(cursor="")
            self._convert_progressbar["value"] = 100
            messagebox.showerror("Error", ex)
        else:
            cur = con.cursor()
            database_name = StringDialog.ask_string(title_="Database name", prompt="Name for database:")
            if database_name is not None:
                if database_name == "":
                    messagebox.showinfo("Error", "Database name field cannot be blank")
                else:
                    self._convert_progressbar["value"] = 66
                    accesshandler = Accesshandler(cur)
                    accesshandler.make_file(self._output_dir, database_name, self._db_type.get())
                    messagebox.showinfo("Completed", "SQL file generated successfully")
            cur.close()
            con.close()
            self._master.config(cursor="")
            self._convert_progressbar["value"] = 100

    def _set_file_path(self, file_path):
        """Setter for file path, updates file path label and output dir based on _same_dir"""
        self._file_path = file_path
        self._filename_path_label.config(text=self._file_path)
        if self._is_same_dir() == 1:
            self._set_output_dir(path.dirname(self._file_path))
        if self._file_path != "" and self._output_dir != "":        # Enable convert button if it´s good to go
            self._convert_button.config(state="normal")
        self._update_gui_size()

    def _set_output_dir(self, output_dir):
        """Setter for output dir"""
        self._output_dir = output_dir
        self._output_dir_label.config(text=self._output_dir)
        if self._file_path != "" and self._output_dir != "":        # Enable convert button if it´s good to go
            self._convert_button.config(state="normal")
        self._update_gui_size()

    def _update_gui_size(self):
        """Method for expanding or shrinking GUI based on the length of the paths"""
        min_size = 50        # Standard min-size on creating GUI for _filename_path_label ...
        size = max(self._file_path.__len__(), self._output_dir.__len__())
        if size > min_size:
            self._filename_path_label.config(width=size)
            self._output_dir_label.config(width=size)
            self._same_dir_as_file_checkbox.config(width=size)
            self._password_entry.config(width=int((size * 58) / min_size))
            self._convert_button.config(width=int((size * 84) / min_size))
            self._convert_progressbar.config(length=int((size * 512) / min_size))
        elif size <= min_size:
            self._filename_path_label.config(width=50)
            self._output_dir_label.config(width=50)
            self._same_dir_as_file_checkbox.config(width=50)
            self._password_entry.config(width=58)
            self._convert_button.config(width=84)
            self._convert_progressbar.config(length=512)

    def _same_dir_as_file(self):
        """Functionality for disabling or enabling browse output dir button
        and setting _output_dir based on _file_path
        """
        if self._is_same_dir() == 1:
            self._set_output_dir(path.dirname(self._file_path))
            self._browse_dir_button.config(state="disabled")
        else:
            self._browse_dir_button.config(state="normal")

    def _show_hide_password(self):
        """Show/Hide password by current _show_password value and updates it"""
        if self._show_password:
            self._password_entry.config(show="*")
            try:
                self._password_show_image = PhotoImage(file=path.join(module_dir, "images\\watch_pwd.png")).subsample(8, 8)
            except Exception as ex:
                messagebox.showerror("Error", ex)
        else:
            self._password_entry.config(show="")
            try:
                self._password_show_image = PhotoImage(file=path.join(module_dir, "images\\hide_pwd.png")).subsample(8, 8)
            except Exception as ex:
                messagebox.showerror("Error", ex)
        self._password_show_button.config(image=self._password_show_image)
        self._show_password = not self._show_password

    def _browse_file(self):
        """Browse file functionality"""
        file_path = askopenfilename(filetypes=[("Microsoft Access", ".mdb .accdb")])
        if file_path != "":        # If browse window is closed then don´t update
            self._set_file_path(file_path)

    def _browse_dir(self):
        """Browse dir functionality"""
        output_dir = askdirectory()
        if output_dir != "":        # If browse window is closed then don´t update
            self._set_output_dir(output_dir)

    def _get_password(self):
        """Getter for password"""
        return self._password_entry.get()

    def _is_same_dir(self):
        """Getter for _same_dir"""
        return self._same_dir.get()

    @staticmethod
    def DB_TYPE_POSTGRESQL():
        """POSTGRESQL constant"""
        return 1

    @staticmethod
    def DB_TYPE_MARIADB():
        """MARIADB constant"""
        return 2

    @staticmethod
    def DB_TYPE_MYSQL():
        """MYSQL constant"""
        return 3