From df8ba1d495c9e9d046fac95787770b374d40b4aa Mon Sep 17 00:00:00 2001 From: Theo Kroening Date: Tue, 16 Apr 2024 11:59:28 -0400 Subject: [PATCH 01/10] Fixed icon-related crashes on Linux --- Main.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/Main.py b/Main.py index 604ebae..a1f9146 100644 --- a/Main.py +++ b/Main.py @@ -8,10 +8,20 @@ from os.path import exists from os import listdir from tkcalendar import DateEntry +from PIL import Image, ImageTk # safeguard for the treeview automated string conversion problem PREFIX = '<@!PREFIX>' +def setIcon(object): + """ + This function sets the icon of a tkinter window (passed in as `object`) to + the CFM icon. + """ + im = Image.open('assets/CFM.ico') + photo = ImageTk.PhotoImage(im) + object.wm_iconphoto(True, photo) + # change to desired resolution def set_resolution(window, width, height): @@ -287,7 +297,7 @@ def __init__(self, *args, **kwargs): # global window customization self.title('Counter for Messenger') - self.iconbitmap('assets/CFM.ico') + setIcon(self) # frame container setup self.container = tk.Frame(self) @@ -447,7 +457,7 @@ def __init__(self, controller): # profile window customization self.title(self.module.TITLE_PROFILE) - self.iconbitmap('assets/CFM.ico') + setIcon(self) self.focus_set() self.grab_set() @@ -501,7 +511,7 @@ def __init__(self, controller): # settings window customization self.title(self.module.TITLE_SETTINGS) - self.iconbitmap('assets/CFM.ico') + setIcon(self) self.focus_set() self.grab_set() @@ -607,7 +617,7 @@ def __init__(self, controller, chat_total, treeview): # loading window customization self.title(f'{self.module.TITLE_LOADING}...') - self.iconbitmap('assets/CFM.ico') + setIcon(self) self.resizable(False, False) self.focus_set() self.grab_set() @@ -678,7 +688,7 @@ def __init__(self, controller, selection): # statistics window customization self.title(self.module.TITLE_STATISTICS) - self.iconbitmap('assets/CFM.ico') + setIcon(self) self.focus_set() self.grab_set() From aa27ec67ca8ee5aa9bddd11274f1eb377e7c53dc Mon Sep 17 00:00:00 2001 From: Chen3018 Date: Tue, 23 Apr 2024 00:02:51 -0400 Subject: [PATCH 02/10] Backend function to filter the rows --- Main.py | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/Main.py b/Main.py index a1f9146..fcf1504 100644 --- a/Main.py +++ b/Main.py @@ -148,6 +148,32 @@ def __init__(self, parent, controller): self.treeview.bind('', lambda event: self.deselect()) self.treeview.bind('', lambda event: self.show_statistics()) + # *Ordered* list of columns (so we can display them in a fixed order) + self.columns = [ + 'name', + 'pep', + 'type', + 'msg', + 'call', + 'photos', + 'gifs', + 'videos', + 'files' + ] + self.column_titles = columns + + # filter columns + self.filter_columns = { + 'name': '', + 'pep': set(), + 'msg': -1, + 'call': -1, + 'photos': -1, + 'gifs': -1, + 'videos': -1, + 'files': -1 + } + # show frame title ttk.Label( self.main, text=f'{self.module.TITLE_NUMBER_OF_MSGS}: ', foreground='#ffffff', background='#232323', @@ -251,6 +277,39 @@ def sort_treeview(self, column, order, bias): # Reverse the order for the next sort self.treeview.heading(column, command=lambda: self.sort_treeview(column, not order, bias)) + def filter_treeview(self): + """ + This function filters out rows based on criterias selected by the user. + Example: Show messages with more than 10 photos, but less than 50. + """ + + # Retrieve all the rows in the treeview + children = self.treeview.get_children('') + + filtered = [] + + for child in children: + # Check if user wants to filter by name + if self.filter_columns['name'] != '': + if child['name'] != self.filter_columns['name']: + filtered.append(child) + + # Check if user wants to filter by participants + if self.filter_columns['pep'] != {}: + # only append if no participants are in the list + if self.filter_columns['pep'].isdisjoint(child['pep']): + filtered.append(child) + + for column_name in ['msg', 'call', 'photos', 'gifs', 'videos', 'files']: + # Check if user wants to filter by messages + if self.filter_columns[column_name] != -1: + min, max = self.filter_columns[column_name] + if min <= child[column_name] <= max: + filtered.append(child) + + # Set the selection to the filtered list + self.treeview.selection_set(filtered) + # invoked on double left click on any treeview listing def show_statistics(self): try: From 0624f9087fdfbbbbec7eaf59fa2713ce35fe3f4b Mon Sep 17 00:00:00 2001 From: choksis8168 Date: Sat, 27 Apr 2024 16:27:31 -0400 Subject: [PATCH 03/10] Getting filter popup to show up and format correctly --- Main.py | 139 +++++++++++++++++- langs/Deutsch.py | 1 + langs/English.py | 1 + "langs/Espa\303\261ol.py" | 1 + langs/Francais.py | 1 + langs/Hindi.py | 1 + langs/Marathi.py | 1 + langs/Nederlands.py | 1 + langs/Polski.py | 1 + langs/Slovensky.py | 1 + langs/Tagalog.py | 1 + ...16\267\316\275\316\271\316\272\316\254.py" | 1 + ...30\271\330\261\330\250\331\212\330\251.py" | 1 + 13 files changed, 146 insertions(+), 5 deletions(-) diff --git a/Main.py b/Main.py index fcf1504..be9a596 100644 --- a/Main.py +++ b/Main.py @@ -9,6 +9,7 @@ from os import listdir from tkcalendar import DateEntry from PIL import Image, ImageTk +import platform # safeguard for the treeview automated string conversion problem PREFIX = '<@!PREFIX>' @@ -199,6 +200,11 @@ def __init__(self, parent, controller): command=self.search ).pack(side='top', pady=10) + # show filter button + ttk.Button( + self.nav, text=self.module.TITLE_FILTER, padding=5, command=lambda: FilterPopup(self.controller, self.columns, self.column_titles) + ).pack(side='top', pady=10) + # show exit button ttk.Button( self.nav, image=self.controller.ICON_EXIT, text=self.module.TITLE_EXIT, compound='left', padding=5, @@ -356,7 +362,7 @@ def __init__(self, *args, **kwargs): # global window customization self.title('Counter for Messenger') - setIcon(self) + self.iconbitmap('assets/CFM.ico') # frame container setup self.container = tk.Frame(self) @@ -516,7 +522,7 @@ def __init__(self, controller): # profile window customization self.title(self.module.TITLE_PROFILE) - setIcon(self) + self.iconbitmap('assets/CFM.ico') self.focus_set() self.grab_set() @@ -570,7 +576,7 @@ def __init__(self, controller): # settings window customization self.title(self.module.TITLE_SETTINGS) - setIcon(self) + self.iconbitmap('assets/CFM.ico') self.focus_set() self.grab_set() @@ -676,7 +682,7 @@ def __init__(self, controller, chat_total, treeview): # loading window customization self.title(f'{self.module.TITLE_LOADING}...') - setIcon(self) + self.iconbitmap('assets/CFM.ico') self.resizable(False, False) self.focus_set() self.grab_set() @@ -747,7 +753,7 @@ def __init__(self, controller, selection): # statistics window customization self.title(self.module.TITLE_STATISTICS) - setIcon(self) + self.iconbitmap('assets/CFM.ico') self.focus_set() self.grab_set() @@ -807,6 +813,129 @@ def __init__(self, controller, selection): listbox.insert('end', f'{self.module.TITLE_PER_MONTH} - {all_msgs / (sec_since_start / (30 * 86400)):.2f}') listbox.insert('end', f'{self.module.TITLE_PER_YEAR} - {all_msgs / (sec_since_start / (365 * 86400)):.2f}') +class FilterPopup(tk.Toplevel): + """ + This class implements the sort-editor popup + """ + def __init__(self, controller, columns, column_titles): + tk.Toplevel.__init__(self) + self.controller = controller + self.module = self.controller.lang_mdl + set_resolution(self, 800, 600) + + self.columns = columns + print(self.columns) + self.column_titles = column_titles + + self.title(self.module.TITLE_FILTER) + self.focus_set() + self.grab_set() + + self.filter_entries = {} # Dictionary to store filter entry widgets + + # Create filter GUI elements + ttk.Label(self, text="Filter by:", foreground='#000000', background='#ffffff', font=('Arial', 15)).pack(side='top', pady=10) + + + self.canvas = tk.Canvas(self, borderwidth=0) #place canvas on self + self.viewPort = tk.Frame(self.canvas) #place a frame on the canvas, this frame will hold the child widgets + self.vsb = tk.Scrollbar(self, orient="vertical", command=self.canvas.yview) #place a scrollbar on self + self.canvas.configure(yscrollcommand=self.vsb.set) #attach scrollbar action to scroll of canvas + + self.vsb.pack(side="right", fill="y") #pack scrollbar to right of self + self.canvas.pack(side="left", fill="both", expand=True) #pack canvas to left of self and expand to fil + self.canvas_window = self.canvas.create_window((4,4), window=self.viewPort, anchor="nw", #add view port frame to canvas + tags="self.viewPort") + + self.viewPort.bind("", self.onFrameConfigure) #bind an event whenever the size of the viewPort frame changes. + self.canvas.bind("", self.onCanvasConfigure) #bind an event whenever the size of the canvas frame changes. + + self.viewPort.bind('', self.onEnter) # bind wheel events when the cursor enters the control + self.viewPort.bind('', self.onLeave) # unbind wheel events when the cursorl leaves the control + + self.onFrameConfigure(None) #perform an initial stretch on render, otherwise the scroll region has a tiny border until the first resize + for column in columns: + label_width = len(self.column_titles[column]) + 2 + if column in ['name', 'pep']: + label = ttk.Label(self.viewPort, text=self.column_titles[column], foreground='#000000', background='#ffffff', anchor="center", width = label_width) + label.pack(side='top', pady=5) # Fill the label horizontally + entry = ttk.Entry(self.viewPort, width=30) + entry.pack(side='top', pady=5) # Fill the entry horizontally + self.filter_entries[column] = entry + else: # For numerical fields + + label = ttk.Label(self.viewPort, text=f"{self.column_titles[column]}:", foreground='#000000', background='#ffffff', anchor="center", width = label_width) + label.pack(side='top', pady=5) # Fill the label horizontally + label_frame = ttk.Frame(self.viewPort) + label_frame.pack(side='top', pady=5) # Fill and expand the label frame horizontally + container_frame = ttk.Frame(label_frame) + container_frame.pack() + min_label = ttk.Label(container_frame, text="Min:") + # min_label.pack(side='left', padx=(0, 10), expand=True) + min_label.grid(row=0, column=0, padx=(0, 5)) + min_entry = ttk.Entry(container_frame, width=5) + # min_entry.pack(side='left', padx=(0, 10), fill='x') # Fill the entry horizontally + min_entry.grid(row=0, column=1, padx=(0, 5)) + max_label = ttk.Label(container_frame, text="Max:") + # max_label.pack(side='left', padx=(0, 10), expand=True) + max_label.grid(row=0, column=2, padx=(0, 5)) + max_entry = ttk.Entry(container_frame, width=5) + # max_entry.pack(side='left', padx=(0, 10), fill='x') # Fill the entry horizontally + max_entry.grid(row=0, column=3, padx=(0, 5)) + container_frame.grid_columnconfigure(0, weight=1) + self.filter_entries[column] = (min_entry, max_entry) + + def onFrameConfigure(self, event): + '''Reset the scroll region to encompass the inner frame''' + self.canvas.configure(scrollregion=self.canvas.bbox("all")) #whenever the size of the frame changes, alter the scroll region respectively. + + def onCanvasConfigure(self, event): + '''Reset the canvas window to encompass inner frame when required''' + canvas_width = event.width + self.canvas.itemconfig(self.canvas_window, width = canvas_width) #whenever the size of the canvas changes alter the window region respectively. + + def onMouseWheel(self, event): # cross platform scroll wheel event + if platform.system() == 'Windows': + self.canvas.yview_scroll(int(-1* (event.delta/120)), "units") + elif platform.system() == 'Darwin': + self.canvas.yview_scroll(int(-1 * event.delta), "units") + else: + if event.num == 4: + self.canvas.yview_scroll( -1, "units" ) + elif event.num == 5: + self.canvas.yview_scroll( 1, "units" ) + + def onEnter(self, event): # bind wheel events when the cursor enters the control + if platform.system() == 'Linux': + self.canvas.bind_all("", self.onMouseWheel) + self.canvas.bind_all("", self.onMouseWheel) + else: + self.canvas.bind_all("", self.onMouseWheel) + + def onLeave(self, event): # unbind wheel events when the cursorl leaves the control + if platform.system() == 'Linux': + self.canvas.unbind_all("") + self.canvas.unbind_all("") + else: + self.canvas.unbind_all("") + + + + + def apply_filters(self): + # Gather filter criteria from entry widgets + filter_criteria = {} + for column, entry in self.filter_entries.items(): + if column in ['name', 'pep']: + filter_criteria[column] = entry.get() + else: # For numerical fields + min_val = entry[0].get() + max_val = entry[1].get() + filter_criteria[column] = (int(min_val) if min_val else -1, int(max_val) if max_val else -1) + + + + if __name__ == '__main__': MasterWindow().mainloop() diff --git a/langs/Deutsch.py b/langs/Deutsch.py index 1db7829..323de34 100644 --- a/langs/Deutsch.py +++ b/langs/Deutsch.py @@ -14,6 +14,7 @@ TITLE_HOME = 'Heim' TITLE_UPLOAD_MESSAGES = 'Nachrichten hochladen' TITLE_SEARCH = 'Suchen' +TITLE_FILTER = 'Filter' TITLE_EXIT = 'Ausfahrt' TITLE_SETTINGS = 'Einstellungen' TITLE_PROFILE = 'Mein Profil' diff --git a/langs/English.py b/langs/English.py index 9d3cc50..8d33bb8 100644 --- a/langs/English.py +++ b/langs/English.py @@ -14,6 +14,7 @@ TITLE_HOME = 'Home' TITLE_UPLOAD_MESSAGES = 'Upload messages' TITLE_SEARCH = 'Search' +TITLE_FILTER = 'Filter' TITLE_EXIT = 'Exit' TITLE_SETTINGS = 'Settings' TITLE_PROFILE = 'My profile' diff --git "a/langs/Espa\303\261ol.py" "b/langs/Espa\303\261ol.py" index 6700c40..2477333 100644 --- "a/langs/Espa\303\261ol.py" +++ "b/langs/Espa\303\261ol.py" @@ -14,6 +14,7 @@ TITLE_HOME = 'Inicio' TITLE_UPLOAD_MESSAGES = 'Subir mensajes' TITLE_SEARCH = 'Buscar' +TITLE_FILTER = 'Filter' TITLE_EXIT = 'Salir' TITLE_SETTINGS = 'Ajustes' TITLE_PROFILE = 'Mi perfil' diff --git a/langs/Francais.py b/langs/Francais.py index 874c814..7789ae1 100644 --- a/langs/Francais.py +++ b/langs/Francais.py @@ -14,6 +14,7 @@ TITLE_HOME = 'Accueil' TITLE_UPLOAD_MESSAGES = 'Télécharger les messages' TITLE_SEARCH = 'Recherche' +TITLE_FILTER = 'Filter' TITLE_EXIT = 'Quitter' TITLE_SETTINGS = 'Paramètres' TITLE_PROFILE = 'Mon profil' diff --git a/langs/Hindi.py b/langs/Hindi.py index 6cdad8f..9769901 100644 --- a/langs/Hindi.py +++ b/langs/Hindi.py @@ -14,6 +14,7 @@ TITLE_HOME = 'होम' TITLE_UPLOAD_MESSAGES = 'संदेश अपलोड करें' TITLE_SEARCH = 'खोजें' +TITLE_FILTER = 'Filter' TITLE_EXIT = 'बाहर निकलें' TITLE_SETTINGS = 'सेटिंग्स' TITLE_PROFILE = 'मेरी प्रोफ़ाइल' diff --git a/langs/Marathi.py b/langs/Marathi.py index c857b74..4353ffe 100644 --- a/langs/Marathi.py +++ b/langs/Marathi.py @@ -14,6 +14,7 @@ TITLE_HOME = 'मुख्यपृष्ठ' TITLE_UPLOAD_MESSAGES = 'संदेश अपलोड करा' TITLE_SEARCH = 'शोधा' +TITLE_FILTER = 'Filter' TITLE_EXIT = 'बाहेर पडा' TITLE_SETTINGS = 'सेटिंग्ज' TITLE_PROFILE = 'माझे प्रोफाइल' diff --git a/langs/Nederlands.py b/langs/Nederlands.py index 634445c..c1ddfa8 100644 --- a/langs/Nederlands.py +++ b/langs/Nederlands.py @@ -14,6 +14,7 @@ TITLE_HOME = 'Home' TITLE_UPLOAD_MESSAGES = 'Berichten uploaden' TITLE_SEARCH = 'Zoeken' +TITLE_FILTER = 'Filter' TITLE_EXIT = 'Verlaat' TITLE_SETTINGS = 'Instellingen' TITLE_PROFILE = 'Mijn profiel' diff --git a/langs/Polski.py b/langs/Polski.py index 42c2cba..fbbb184 100644 --- a/langs/Polski.py +++ b/langs/Polski.py @@ -14,6 +14,7 @@ TITLE_HOME = 'Strona główna' TITLE_UPLOAD_MESSAGES = 'Załaduj wiadomości' TITLE_SEARCH = 'Szukaj' +TITLE_FILTER = 'Filter' TITLE_EXIT = 'Wyjście' TITLE_SETTINGS = 'Ustawienia' TITLE_PROFILE = 'Mój profil' diff --git a/langs/Slovensky.py b/langs/Slovensky.py index 6d3c2d0..6836c23 100644 --- a/langs/Slovensky.py +++ b/langs/Slovensky.py @@ -14,6 +14,7 @@ TITLE_HOME = 'Domov' TITLE_UPLOAD_MESSAGES = 'Nahrať správy' TITLE_SEARCH = 'Hľadať' +TITLE_FILTER = 'Filter' TITLE_EXIT = 'Ukončiť' TITLE_SETTINGS = 'Nastavenia' TITLE_PROFILE = 'Môj profil' diff --git a/langs/Tagalog.py b/langs/Tagalog.py index 961a11b..27010e3 100644 --- a/langs/Tagalog.py +++ b/langs/Tagalog.py @@ -14,6 +14,7 @@ TITLE_HOME = 'Bahay' TITLE_UPLOAD_MESSAGES = 'i-upload ang iyong mga mensahe' TITLE_SEARCH = 'Paghanap' +TITLE_FILTER = 'Filter' TITLE_EXIT = 'Labasan' TITLE_SETTINGS = 'Mga setting' TITLE_PROFILE = 'Aking profile' diff --git "a/langs/\316\225\316\273\316\273\316\267\316\275\316\271\316\272\316\254.py" "b/langs/\316\225\316\273\316\273\316\267\316\275\316\271\316\272\316\254.py" index a4e92da..0956caa 100644 --- "a/langs/\316\225\316\273\316\273\316\267\316\275\316\271\316\272\316\254.py" +++ "b/langs/\316\225\316\273\316\273\316\267\316\275\316\271\316\272\316\254.py" @@ -14,6 +14,7 @@ TITLE_HOME = 'Αρχική' TITLE_UPLOAD_MESSAGES = 'Ανέβασμα μηνυμάτων' TITLE_SEARCH = 'Αναζήτηση' +TITLE_FILTER = 'Filter' TITLE_EXIT = 'Έξοδος' TITLE_SETTINGS = 'Ρυθμίσεις' TITLE_PROFILE = 'Το προφίλ μου' diff --git "a/langs/\330\247\331\204\330\271\330\261\330\250\331\212\330\251.py" "b/langs/\330\247\331\204\330\271\330\261\330\250\331\212\330\251.py" index 18c7250..b434db0 100644 --- "a/langs/\330\247\331\204\330\271\330\261\330\250\331\212\330\251.py" +++ "b/langs/\330\247\331\204\330\271\330\261\330\250\331\212\330\251.py" @@ -14,6 +14,7 @@ TITLE_HOME = 'الصفحة الرئيسية' TITLE_UPLOAD_MESSAGES = 'تحميل الرسائل' TITLE_SEARCH = 'بحث' +TITLE_FILTER = 'Filter' TITLE_EXIT = 'خروج' TITLE_SETTINGS = 'الإعدادات' TITLE_PROFILE = 'الملف الخاص' From de6b84bc1880395209a18e62067485014eb96097 Mon Sep 17 00:00:00 2001 From: choksis8168 Date: Sat, 27 Apr 2024 16:41:45 -0400 Subject: [PATCH 04/10] connecting frontend to call filter_treeview errors in indexing in filter_treeview function --- Main.py | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/Main.py b/Main.py index be9a596..fa423f3 100644 --- a/Main.py +++ b/Main.py @@ -202,7 +202,7 @@ def __init__(self, parent, controller): # show filter button ttk.Button( - self.nav, text=self.module.TITLE_FILTER, padding=5, command=lambda: FilterPopup(self.controller, self.columns, self.column_titles) + self.nav, text=self.module.TITLE_FILTER, padding=5, command=lambda: FilterPopup(self.controller, self.columns, self.column_titles, self.filter_columns, lambda : self.filter_treeview()) ).pack(side='top', pady=10) # show exit button @@ -288,7 +288,7 @@ def filter_treeview(self): This function filters out rows based on criterias selected by the user. Example: Show messages with more than 10 photos, but less than 50. """ - + print(self.filter_columns) # Retrieve all the rows in the treeview children = self.treeview.get_children('') @@ -817,7 +817,7 @@ class FilterPopup(tk.Toplevel): """ This class implements the sort-editor popup """ - def __init__(self, controller, columns, column_titles): + def __init__(self, controller, columns, column_titles, filter_columns, apply_callback): tk.Toplevel.__init__(self) self.controller = controller self.module = self.controller.lang_mdl @@ -832,6 +832,9 @@ def __init__(self, controller, columns, column_titles): self.grab_set() self.filter_entries = {} # Dictionary to store filter entry widgets + self.filter_columns = filter_columns + + self.apply_callback = apply_callback # Create filter GUI elements ttk.Label(self, text="Filter by:", foreground='#000000', background='#ffffff', font=('Arial', 15)).pack(side='top', pady=10) @@ -866,24 +869,29 @@ def __init__(self, controller, columns, column_titles): label = ttk.Label(self.viewPort, text=f"{self.column_titles[column]}:", foreground='#000000', background='#ffffff', anchor="center", width = label_width) label.pack(side='top', pady=5) # Fill the label horizontally + label_frame = ttk.Frame(self.viewPort) label_frame.pack(side='top', pady=5) # Fill and expand the label frame horizontally container_frame = ttk.Frame(label_frame) container_frame.pack() + min_label = ttk.Label(container_frame, text="Min:") - # min_label.pack(side='left', padx=(0, 10), expand=True) min_label.grid(row=0, column=0, padx=(0, 5)) + min_entry = ttk.Entry(container_frame, width=5) - # min_entry.pack(side='left', padx=(0, 10), fill='x') # Fill the entry horizontally min_entry.grid(row=0, column=1, padx=(0, 5)) + max_label = ttk.Label(container_frame, text="Max:") - # max_label.pack(side='left', padx=(0, 10), expand=True) max_label.grid(row=0, column=2, padx=(0, 5)) + max_entry = ttk.Entry(container_frame, width=5) - # max_entry.pack(side='left', padx=(0, 10), fill='x') # Fill the entry horizontally max_entry.grid(row=0, column=3, padx=(0, 5)) + container_frame.grid_columnconfigure(0, weight=1) self.filter_entries[column] = (min_entry, max_entry) + + apply_button = ttk.Button(self.viewPort, text="Apply Filters", command=self.apply_filters) + apply_button.pack(fill=tk.X, pady=10) # Fill the button horizontally def onFrameConfigure(self, event): '''Reset the scroll region to encompass the inner frame''' @@ -920,20 +928,15 @@ def onLeave(self, event): self.canvas.unbind_all("") - - def apply_filters(self): - # Gather filter criteria from entry widgets - filter_criteria = {} for column, entry in self.filter_entries.items(): if column in ['name', 'pep']: - filter_criteria[column] = entry.get() + self.filter_columns[column] = entry.get() else: # For numerical fields min_val = entry[0].get() max_val = entry[1].get() - filter_criteria[column] = (int(min_val) if min_val else -1, int(max_val) if max_val else -1) - - + self.filter_columns[column] = (int(min_val) if min_val else -1, int(max_val) if max_val else -1) + self.apply_callback() From 729fe0c3ba3d45302152148f54231d5179eab0c6 Mon Sep 17 00:00:00 2001 From: choksis8168 Date: Sat, 27 Apr 2024 17:18:01 -0400 Subject: [PATCH 05/10] fixing logic in filter_treeview --- Main.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/Main.py b/Main.py index fa423f3..2d54dc1 100644 --- a/Main.py +++ b/Main.py @@ -10,6 +10,7 @@ from tkcalendar import DateEntry from PIL import Image, ImageTk import platform +import ast # safeguard for the treeview automated string conversion problem PREFIX = '<@!PREFIX>' @@ -288,31 +289,34 @@ def filter_treeview(self): This function filters out rows based on criterias selected by the user. Example: Show messages with more than 10 photos, but less than 50. """ - print(self.filter_columns) # Retrieve all the rows in the treeview children = self.treeview.get_children('') + column_headers = self.treeview['columns'] + filtered = [] for child in children: + row_content = self.treeview.item(child)['values'] + row_dict = {column_header: value for column_header, value in zip(column_headers, row_content)} + # Check if user wants to filter by name - if self.filter_columns['name'] != '': - if child['name'] != self.filter_columns['name']: + if self.filter_columns['name'] == '' or row_dict['name'] == self.filter_columns['name']: filtered.append(child) # Check if user wants to filter by participants - if self.filter_columns['pep'] != {}: - # only append if no participants are in the list - if self.filter_columns['pep'].isdisjoint(child['pep']): + if self.filter_columns['pep'] == {} or self.filter_columns['pep'] == ast.literal_eval(row_dict['pep']): filtered.append(child) for column_name in ['msg', 'call', 'photos', 'gifs', 'videos', 'files']: # Check if user wants to filter by messages - if self.filter_columns[column_name] != -1: + if self.filter_columns[column_name] == -1: + filtered.append(child) + else: min, max = self.filter_columns[column_name] - if min <= child[column_name] <= max: + if min <= row_dict[column_name] <= max: filtered.append(child) - + # Set the selection to the filtered list self.treeview.selection_set(filtered) @@ -930,8 +934,10 @@ def onLeave(self, event): def apply_filters(self): for column, entry in self.filter_entries.items(): - if column in ['name', 'pep']: + if column == 'name': self.filter_columns[column] = entry.get() + elif column == 'pep': + self.filter_columns[column] = set(entry.get().split(',')) else: # For numerical fields min_val = entry[0].get() max_val = entry[1].get() From 12b105ec67da816bc897104d72ae3d831cd627e0 Mon Sep 17 00:00:00 2001 From: choksis8168 Date: Sun, 28 Apr 2024 12:03:06 -0400 Subject: [PATCH 06/10] more fixes to the filter treeview function, clear filter button --- Main.py | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/Main.py b/Main.py index 2d54dc1..c25ebe8 100644 --- a/Main.py +++ b/Main.py @@ -206,6 +206,14 @@ def __init__(self, parent, controller): self.nav, text=self.module.TITLE_FILTER, padding=5, command=lambda: FilterPopup(self.controller, self.columns, self.column_titles, self.filter_columns, lambda : self.filter_treeview()) ).pack(side='top', pady=10) + # clear selection button + ttk.Button( + self.nav, + text="Clear Filters", + padding=5, + command=self.deselect + ).pack(side='top', pady=10) + # show exit button ttk.Button( self.nav, image=self.controller.ICON_EXIT, text=self.module.TITLE_EXIT, compound='left', padding=5, @@ -300,23 +308,24 @@ def filter_treeview(self): row_content = self.treeview.item(child)['values'] row_dict = {column_header: value for column_header, value in zip(column_headers, row_content)} + keepRow = True # Check if user wants to filter by name - if self.filter_columns['name'] == '' or row_dict['name'] == self.filter_columns['name']: - filtered.append(child) + if self.filter_columns['name'] != '' and row_dict['name'] != self.filter_columns['name']: + keepRow = False # Check if user wants to filter by participants - if self.filter_columns['pep'] == {} or self.filter_columns['pep'] == ast.literal_eval(row_dict['pep']): - filtered.append(child) + if self.filter_columns['pep'] != {''} and not self.filter_columns['pep'].issubset(ast.literal_eval(row_dict['pep'])): + keepRow = False for column_name in ['msg', 'call', 'photos', 'gifs', 'videos', 'files']: # Check if user wants to filter by messages - if self.filter_columns[column_name] == -1: - filtered.append(child) - else: + if self.filter_columns[column_name] != (-1, -1): min, max = self.filter_columns[column_name] - if min <= row_dict[column_name] <= max: - filtered.append(child) - + if (min != -1 and row_dict[column_name] < min) or (max != -1 and row_dict[column_name] > max): + keepRow = False + if (keepRow): + filtered.append(child) + # # Set the selection to the filtered list self.treeview.selection_set(filtered) @@ -863,7 +872,7 @@ def __init__(self, controller, columns, column_titles, filter_columns, apply_cal self.onFrameConfigure(None) #perform an initial stretch on render, otherwise the scroll region has a tiny border until the first resize for column in columns: label_width = len(self.column_titles[column]) + 2 - if column in ['name', 'pep']: + if column in ['name', 'pep', 'type']: label = ttk.Label(self.viewPort, text=self.column_titles[column], foreground='#000000', background='#ffffff', anchor="center", width = label_width) label.pack(side='top', pady=5) # Fill the label horizontally entry = ttk.Entry(self.viewPort, width=30) @@ -937,12 +946,14 @@ def apply_filters(self): if column == 'name': self.filter_columns[column] = entry.get() elif column == 'pep': - self.filter_columns[column] = set(entry.get().split(',')) + participant_names = [participant.strip() for participant in entry.get().split(',')] + self.filter_columns[column] = set(participant_names) else: # For numerical fields min_val = entry[0].get() max_val = entry[1].get() self.filter_columns[column] = (int(min_val) if min_val else -1, int(max_val) if max_val else -1) self.apply_callback() + self.destroy() From 95e45c8f081e213e29a63228ed95d0109c2000bb Mon Sep 17 00:00:00 2001 From: choksis8168 Date: Sun, 28 Apr 2024 13:06:52 -0400 Subject: [PATCH 07/10] bug fix for entering min and max values --- Main.py | 60 +++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 23 deletions(-) diff --git a/Main.py b/Main.py index c25ebe8..8bab868 100644 --- a/Main.py +++ b/Main.py @@ -853,23 +853,34 @@ def __init__(self, controller, columns, column_titles, filter_columns, apply_cal ttk.Label(self, text="Filter by:", foreground='#000000', background='#ffffff', font=('Arial', 15)).pack(side='top', pady=10) - self.canvas = tk.Canvas(self, borderwidth=0) #place canvas on self - self.viewPort = tk.Frame(self.canvas) #place a frame on the canvas, this frame will hold the child widgets - self.vsb = tk.Scrollbar(self, orient="vertical", command=self.canvas.yview) #place a scrollbar on self - self.canvas.configure(yscrollcommand=self.vsb.set) #attach scrollbar action to scroll of canvas - - self.vsb.pack(side="right", fill="y") #pack scrollbar to right of self - self.canvas.pack(side="left", fill="both", expand=True) #pack canvas to left of self and expand to fil - self.canvas_window = self.canvas.create_window((4,4), window=self.viewPort, anchor="nw", #add view port frame to canvas + self.canvas = tk.Canvas(self, borderwidth=0) + # place a frame on the canvas, this frame will hold the child widgets + self.viewPort = tk.Frame(self.canvas) + # place a scrollbar on self + self.vsb = tk.Scrollbar(self, orient="vertical", command=self.canvas.yview) + # attach scrollbar action to scroll of canvas + self.canvas.configure(yscrollcommand=self.vsb.set) + + # pack scrollbar to right of self + self.vsb.pack(side="right", fill="y") + # pack canvas to left of self and expand to fil + self.canvas.pack(side="left", fill="both", expand=True) + # add view port frame to canvas + self.canvas_window = self.canvas.create_window((4,4), window=self.viewPort, anchor="nw", tags="self.viewPort") - - self.viewPort.bind("", self.onFrameConfigure) #bind an event whenever the size of the viewPort frame changes. - self.canvas.bind("", self.onCanvasConfigure) #bind an event whenever the size of the canvas frame changes. - - self.viewPort.bind('', self.onEnter) # bind wheel events when the cursor enters the control - self.viewPort.bind('', self.onLeave) # unbind wheel events when the cursorl leaves the control - - self.onFrameConfigure(None) #perform an initial stretch on render, otherwise the scroll region has a tiny border until the first resize + + # bind an event whenever the size of the viewPort frame changes. + self.viewPort.bind("", self.onFrameConfigure) + # bind an event whenever the size of the canvas frame changes. + self.canvas.bind("", self.onCanvasConfigure) + # bind wheel events when the cursor enters the control + self.viewPort.bind('', self.onEnter) + # unbind wheel events when the cursorl leaves the control + self.viewPort.bind('', self.onLeave) + + # perform an initial stretch on render, otherwise the scroll region + # has a tiny border until the first resize + self.onFrameConfigure(None) for column in columns: label_width = len(self.column_titles[column]) + 2 if column in ['name', 'pep', 'type']: @@ -901,21 +912,22 @@ def __init__(self, controller, columns, column_titles, filter_columns, apply_cal max_entry.grid(row=0, column=3, padx=(0, 5)) container_frame.grid_columnconfigure(0, weight=1) - self.filter_entries[column] = (min_entry, max_entry) + self.filter_entries[column] = (min_entry,max_entry) apply_button = ttk.Button(self.viewPort, text="Apply Filters", command=self.apply_filters) apply_button.pack(fill=tk.X, pady=10) # Fill the button horizontally def onFrameConfigure(self, event): '''Reset the scroll region to encompass the inner frame''' - self.canvas.configure(scrollregion=self.canvas.bbox("all")) #whenever the size of the frame changes, alter the scroll region respectively. + self.canvas.configure(scrollregion=self.canvas.bbox("all")) def onCanvasConfigure(self, event): '''Reset the canvas window to encompass inner frame when required''' canvas_width = event.width - self.canvas.itemconfig(self.canvas_window, width = canvas_width) #whenever the size of the canvas changes alter the window region respectively. + self.canvas.itemconfig(self.canvas_window, width = canvas_width) - def onMouseWheel(self, event): # cross platform scroll wheel event + def onMouseWheel(self, event): + # cross platform scroll wheel event if platform.system() == 'Windows': self.canvas.yview_scroll(int(-1* (event.delta/120)), "units") elif platform.system() == 'Darwin': @@ -926,14 +938,16 @@ def onMouseWheel(self, event): elif event.num == 5: self.canvas.yview_scroll( 1, "units" ) - def onEnter(self, event): # bind wheel events when the cursor enters the control + def onEnter(self, event): + # bind wheel events when the cursor enters the control if platform.system() == 'Linux': self.canvas.bind_all("", self.onMouseWheel) self.canvas.bind_all("", self.onMouseWheel) else: self.canvas.bind_all("", self.onMouseWheel) - def onLeave(self, event): # unbind wheel events when the cursorl leaves the control + def onLeave(self, event): + # unbind wheel events when the cursorl leaves the control if platform.system() == 'Linux': self.canvas.unbind_all("") self.canvas.unbind_all("") @@ -943,7 +957,7 @@ def onLeave(self, event): def apply_filters(self): for column, entry in self.filter_entries.items(): - if column == 'name': + if column in ['name', 'type']: self.filter_columns[column] = entry.get() elif column == 'pep': participant_names = [participant.strip() for participant in entry.get().split(',')] From 61726191ea7c2ae117becff12919430fa8d87a5c Mon Sep 17 00:00:00 2001 From: choksis8168 Date: Sun, 28 Apr 2024 13:09:01 -0400 Subject: [PATCH 08/10] Update Main.py --- Main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Main.py b/Main.py index 8bab868..10710db 100644 --- a/Main.py +++ b/Main.py @@ -828,7 +828,7 @@ def __init__(self, controller, selection): class FilterPopup(tk.Toplevel): """ - This class implements the sort-editor popup + This class implements the filter popup """ def __init__(self, controller, columns, column_titles, filter_columns, apply_callback): tk.Toplevel.__init__(self) From 2276b8ce80898db1a633539d39c297c7f49d5c98 Mon Sep 17 00:00:00 2001 From: choksis8168 Date: Sun, 28 Apr 2024 14:36:42 -0400 Subject: [PATCH 09/10] merging master changes --- Main.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/Main.py b/Main.py index 10710db..5691171 100644 --- a/Main.py +++ b/Main.py @@ -762,7 +762,7 @@ def __init__(self, controller, selection): tk.Toplevel.__init__(self) self.controller = controller self.module = self.controller.lang_mdl - set_resolution(self, 800, 600) + set_resolution(self, 800, 1000) # enlarge to contain first five messages # statistics window customization self.title(self.module.TITLE_STATISTICS) @@ -770,11 +770,11 @@ def __init__(self, controller, selection): self.focus_set() self.grab_set() - title, people, room, all_msgs, all_chars, calltime, sent_msgs, start_date, total_photos, total_gifs, total_videos, total_files = self.controller.extract_data( + title, people, room, all_msgs, all_chars, calltime, sent_msgs, start_date, total_photos, total_gifs, total_videos, total_files, first_five_messages = self.controller.extract_data( selection) # resize the window to fit all data if the conversation is a group chat if room == self.module.TITLE_GROUP_CHAT: - set_resolution(self, 800, 650) + set_resolution(self, 800, 1200) # enlarge to contain first five messages # display popup title ttk.Label(self, text=f'{self.module.TITLE_MSG_STATS}:').pack(side='top', pady=16) # show conversation title and type @@ -826,6 +826,27 @@ def __init__(self, controller, selection): listbox.insert('end', f'{self.module.TITLE_PER_MONTH} - {all_msgs / (sec_since_start / (30 * 86400)):.2f}') listbox.insert('end', f'{self.module.TITLE_PER_YEAR} - {all_msgs / (sec_since_start / (365 * 86400)):.2f}') + # box to contain first five messages: + ttk.Label( + self, text="First 5 Messages:" + ).pack(side='top', pady=5) + + messages_frame = ttk.Frame(self) # Frame to hold Listbox and Scrollbar for messages + messages_frame.pack(side='top', fill='both', expand=True) + + messages_scrollbar = ttk.Scrollbar(messages_frame) + messages_scrollbar.pack(side='right', fill='y') + + messages_listbox = tk.Listbox(messages_frame, width=50, height=1, yscrollcommand=messages_scrollbar.set) + messages_listbox.pack(side='left', fill='both', expand=True) + messages_scrollbar.config(command=messages_listbox.yview) + + for sender_name, content in first_five_messages: + messages_listbox.insert('end', f"{sender_name}: {content}") + + # add close button to close statistics popup + ttk.Button(self, text="Close", command=self.destroy).pack(side='bottom', pady=10) + class FilterPopup(tk.Toplevel): """ This class implements the filter popup From 8a8da2198536563994d1fe2f1a9c2b36a1485a58 Mon Sep 17 00:00:00 2001 From: Chen3018 Date: Sun, 28 Apr 2024 16:32:54 -0400 Subject: [PATCH 10/10] Added translations for filter for all languages --- "langs/Espa\303\261ol.py" | 2 +- langs/Francais.py | 2 +- langs/Hindi.py | 2 +- langs/Marathi.py | 2 +- langs/Polski.py | 2 +- langs/Tagalog.py | 2 +- ...\316\273\316\273\316\267\316\275\316\271\316\272\316\254.py" | 2 +- ...\330\247\331\204\330\271\330\261\330\250\331\212\330\251.py" | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git "a/langs/Espa\303\261ol.py" "b/langs/Espa\303\261ol.py" index 2477333..abcc06f 100644 --- "a/langs/Espa\303\261ol.py" +++ "b/langs/Espa\303\261ol.py" @@ -14,7 +14,7 @@ TITLE_HOME = 'Inicio' TITLE_UPLOAD_MESSAGES = 'Subir mensajes' TITLE_SEARCH = 'Buscar' -TITLE_FILTER = 'Filter' +TITLE_FILTER = 'Filtro' TITLE_EXIT = 'Salir' TITLE_SETTINGS = 'Ajustes' TITLE_PROFILE = 'Mi perfil' diff --git a/langs/Francais.py b/langs/Francais.py index 7789ae1..5577a74 100644 --- a/langs/Francais.py +++ b/langs/Francais.py @@ -14,7 +14,7 @@ TITLE_HOME = 'Accueil' TITLE_UPLOAD_MESSAGES = 'Télécharger les messages' TITLE_SEARCH = 'Recherche' -TITLE_FILTER = 'Filter' +TITLE_FILTER = 'Filtre' TITLE_EXIT = 'Quitter' TITLE_SETTINGS = 'Paramètres' TITLE_PROFILE = 'Mon profil' diff --git a/langs/Hindi.py b/langs/Hindi.py index 9769901..3140015 100644 --- a/langs/Hindi.py +++ b/langs/Hindi.py @@ -14,7 +14,7 @@ TITLE_HOME = 'होम' TITLE_UPLOAD_MESSAGES = 'संदेश अपलोड करें' TITLE_SEARCH = 'खोजें' -TITLE_FILTER = 'Filter' +TITLE_FILTER = 'फ़िल्टर' TITLE_EXIT = 'बाहर निकलें' TITLE_SETTINGS = 'सेटिंग्स' TITLE_PROFILE = 'मेरी प्रोफ़ाइल' diff --git a/langs/Marathi.py b/langs/Marathi.py index 4353ffe..bf9f611 100644 --- a/langs/Marathi.py +++ b/langs/Marathi.py @@ -14,7 +14,7 @@ TITLE_HOME = 'मुख्यपृष्ठ' TITLE_UPLOAD_MESSAGES = 'संदेश अपलोड करा' TITLE_SEARCH = 'शोधा' -TITLE_FILTER = 'Filter' +TITLE_FILTER = 'फिल्टर' TITLE_EXIT = 'बाहेर पडा' TITLE_SETTINGS = 'सेटिंग्ज' TITLE_PROFILE = 'माझे प्रोफाइल' diff --git a/langs/Polski.py b/langs/Polski.py index fbbb184..8d64862 100644 --- a/langs/Polski.py +++ b/langs/Polski.py @@ -14,7 +14,7 @@ TITLE_HOME = 'Strona główna' TITLE_UPLOAD_MESSAGES = 'Załaduj wiadomości' TITLE_SEARCH = 'Szukaj' -TITLE_FILTER = 'Filter' +TITLE_FILTER = 'Filtr' TITLE_EXIT = 'Wyjście' TITLE_SETTINGS = 'Ustawienia' TITLE_PROFILE = 'Mój profil' diff --git a/langs/Tagalog.py b/langs/Tagalog.py index 27010e3..96b48fe 100644 --- a/langs/Tagalog.py +++ b/langs/Tagalog.py @@ -14,7 +14,7 @@ TITLE_HOME = 'Bahay' TITLE_UPLOAD_MESSAGES = 'i-upload ang iyong mga mensahe' TITLE_SEARCH = 'Paghanap' -TITLE_FILTER = 'Filter' +TITLE_FILTER = 'Salain' TITLE_EXIT = 'Labasan' TITLE_SETTINGS = 'Mga setting' TITLE_PROFILE = 'Aking profile' diff --git "a/langs/\316\225\316\273\316\273\316\267\316\275\316\271\316\272\316\254.py" "b/langs/\316\225\316\273\316\273\316\267\316\275\316\271\316\272\316\254.py" index 0956caa..815afe9 100644 --- "a/langs/\316\225\316\273\316\273\316\267\316\275\316\271\316\272\316\254.py" +++ "b/langs/\316\225\316\273\316\273\316\267\316\275\316\271\316\272\316\254.py" @@ -14,7 +14,7 @@ TITLE_HOME = 'Αρχική' TITLE_UPLOAD_MESSAGES = 'Ανέβασμα μηνυμάτων' TITLE_SEARCH = 'Αναζήτηση' -TITLE_FILTER = 'Filter' +TITLE_FILTER = 'Φίλτρο' TITLE_EXIT = 'Έξοδος' TITLE_SETTINGS = 'Ρυθμίσεις' TITLE_PROFILE = 'Το προφίλ μου' diff --git "a/langs/\330\247\331\204\330\271\330\261\330\250\331\212\330\251.py" "b/langs/\330\247\331\204\330\271\330\261\330\250\331\212\330\251.py" index b434db0..f2b1b02 100644 --- "a/langs/\330\247\331\204\330\271\330\261\330\250\331\212\330\251.py" +++ "b/langs/\330\247\331\204\330\271\330\261\330\250\331\212\330\251.py" @@ -14,7 +14,7 @@ TITLE_HOME = 'الصفحة الرئيسية' TITLE_UPLOAD_MESSAGES = 'تحميل الرسائل' TITLE_SEARCH = 'بحث' -TITLE_FILTER = 'Filter' +TITLE_FILTER = 'فلتر' TITLE_EXIT = 'خروج' TITLE_SETTINGS = 'الإعدادات' TITLE_PROFILE = 'الملف الخاص'