From c5f18b520a61b054adfee2812ec09c615cd7dd86 Mon Sep 17 00:00:00 2001 From: Martin Tranberg Date: Tue, 31 Mar 2026 16:56:10 +0200 Subject: [PATCH] feat: implement natural sorting and column-based list view sorting with visual indicators --- sharepoint_browser.py | 109 +++++++++++++++++++++++++++++++++--------- 1 file changed, 87 insertions(+), 22 deletions(-) diff --git a/sharepoint_browser.py b/sharepoint_browser.py index b8c0036..e6ee565 100644 --- a/sharepoint_browser.py +++ b/sharepoint_browser.py @@ -9,6 +9,7 @@ import msal import wx import wx.lib.newevent import webbrowser +import re # --- STIHÅNDTERING (Til EXE-brug) --- if getattr(sys, 'frozen', False): @@ -246,6 +247,10 @@ STRINGS = { if not os.path.exists(TEMP_DIR): os.makedirs(TEMP_DIR) +def natural_sort_key(s): + return [int(text) if text.isdigit() else text.lower() + for text in re.split('([0-9]+)', str(s))] + class UploadDropTarget(wx.FileDropTarget): def __init__(self, window, app): wx.FileDropTarget.__init__(self) @@ -436,14 +441,25 @@ class SharePointApp(wx.Frame): self.active_edits = {} # item_id -> { "name": name, "event": Event, "waiting": bool } self.favorites = settings.get("favorites", []) self.fav_visible = settings.get("fav_visible", True) + self.sort_col = 0 # Default Name + self.sort_asc = True - # System Ikoner (Brug ART_CMN_DIALOG eller ART_TOOLBAR for mere farve på Windows) + # System Ikoner self.image_list = wx.ImageList(16, 16) - self.idx_site = self.image_list.Add(wx.ArtProvider.GetBitmap(wx.ART_GO_HOME, wx.ART_CMN_DIALOG, (16, 16))) # Site - self.idx_drive = self.image_list.Add(wx.ArtProvider.GetBitmap(wx.ART_HARDDISK, wx.ART_CMN_DIALOG, (16, 16))) # Drive - self.idx_folder = self.image_list.Add(wx.ArtProvider.GetBitmap(wx.ART_FOLDER, wx.ART_CMN_DIALOG, (16, 16))) # Folder - self.idx_file = self.image_list.Add(wx.ArtProvider.GetBitmap(wx.ART_NORMAL_FILE, wx.ART_CMN_DIALOG, (16, 16))) # File - self.idx_star = self.image_list.Add(wx.ArtProvider.GetBitmap(wx.ART_ADD_BOOKMARK, wx.ART_CMN_DIALOG, (16, 16))) # Favorit stjerne + + def add_icon(art_id, client=wx.ART_CMN_DIALOG): + bmp = wx.ArtProvider.GetBitmap(art_id, client, (16, 16)) + if not bmp.IsOk(): + bmp = wx.ArtProvider.GetBitmap(wx.ART_NORMAL_FILE, client, (16, 16)) # Fallback + return self.image_list.Add(bmp) + + self.idx_site = add_icon(wx.ART_GO_HOME) + self.idx_drive = add_icon(wx.ART_HARDDISK) + self.idx_folder = add_icon(wx.ART_FOLDER) + self.idx_file = add_icon(wx.ART_NORMAL_FILE) + self.idx_star = add_icon(wx.ART_ADD_BOOKMARK) + self.idx_up = add_icon(wx.ART_GO_UP, wx.ART_TOOLBAR) + self.idx_down = add_icon(wx.ART_GO_DOWN, wx.ART_TOOLBAR) # Threading/Sync til filredigering @@ -638,6 +654,7 @@ class SharePointApp(wx.Frame): self.list_ctrl.InsertColumn(3, self.get_txt("col_modified"), width=180) self.list_ctrl.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.on_item_activated) + self.list_ctrl.Bind(wx.EVT_LIST_COL_CLICK, self.on_column_click) self.list_ctrl.Bind(wx.EVT_CONTEXT_MENU, self.on_right_click) # AKTIVER DRAG & DROP @@ -1483,25 +1500,73 @@ class SharePointApp(wx.Frame): wx.CallAfter(self._populate_list_ctrl, items_data, data) + def on_column_click(self, event): + col = event.GetColumn() + if col == self.sort_col: + self.sort_asc = not self.sort_asc + else: + self.sort_col = col + self.sort_asc = True + + self.apply_sorting() + + def apply_sorting(self): + if not self.current_items: return + + # Priority: SITE < DRIVE < FOLDER < FILE + type_prio = {"SITE": 0, "DRIVE": 1, "FOLDER": 2, "FILE": 3} + + def sort_logic(item): + # Altid grupper efter type først (mapper øverst) + p = type_prio.get(item['type'], 9) + + val = "" + if self.sort_col == 0: # Name + val = natural_sort_key(item['name']) + elif self.sort_col == 1: # Type + val = item['type'] + elif self.sort_col == 2: # Size + val = item['size'] if item['size'] is not None else -1 + elif self.sort_col == 3: # Modified + val = item['modified'] + + return (p, val) + + self.current_items.sort(key=sort_logic, reverse=not self.sort_asc) + self._update_list_view_only() + + def _update_list_view_only(self): + self.list_ctrl.DeleteAllItems() + for i, item in enumerate(self.current_items): + img_idx = self.idx_file + if item['type'] == "FOLDER": img_idx = self.idx_folder + elif item['type'] == "DRIVE": img_idx = self.idx_drive + elif item['type'] == "SITE": img_idx = self.idx_site + + self.list_ctrl.InsertItem(i, item['name'], img_idx) + type_str = self.get_txt("type_folder") if item['type'] == "FOLDER" else self.get_txt("type_file") if item['type'] == "FILE" else self.get_txt("type_drive") + self.list_ctrl.SetItem(i, 1, type_str) + size_str = format_size(item['size']) if item['size'] is not None else "" + self.list_ctrl.SetItem(i, 2, size_str) + self.list_ctrl.SetItem(i, 3, item['modified']) + + # Opdater kolonne ikoner + for col in range(4): + info = self.list_ctrl.GetColumn(col) + if col == self.sort_col: + info.SetImage(self.idx_up if self.sort_asc else self.idx_down) + else: + info.SetImage(-1) + self.list_ctrl.SetColumn(col, info) + def _populate_list_ctrl(self, items_data, parent_data): if not self: return try: - self.list_ctrl.DeleteAllItems() - self.current_items = [] - for i, item in enumerate(items_data): - img_idx = self.idx_file - if item['type'] == "FOLDER": img_idx = self.idx_folder - elif item['type'] == "DRIVE": img_idx = self.idx_drive - elif item['type'] == "SITE": img_idx = self.idx_site - - self.list_ctrl.InsertItem(i, item['name'], img_idx) - type_str = self.get_txt("type_folder") if item['type'] == "FOLDER" else self.get_txt("type_file") if item['type'] == "FILE" else self.get_txt("type_drive") - self.list_ctrl.SetItem(i, 1, type_str) - size_str = format_size(item['size']) if item['size'] is not None else "" - self.list_ctrl.SetItem(i, 2, size_str) - self.list_ctrl.SetItem(i, 3, item['modified']) - self.current_items.append(item) - + self.current_items = items_data + + # Anvend sortering før visning + self.apply_sorting() + self.set_status(self.get_txt("status_ready")) if parent_data['type'] == "SITE":