diff --git a/sharepoint_browser.py b/sharepoint_browser.py index feba903..f80bf21 100644 --- a/sharepoint_browser.py +++ b/sharepoint_browser.py @@ -10,6 +10,8 @@ import wx import wx.lib.newevent import webbrowser import re +import ctypes +from ctypes import wintypes # --- STIHÅNDTERING (Til EXE-brug) --- if getattr(sys, 'frozen', False): @@ -251,6 +253,20 @@ def natural_sort_key(s): return [int(text) if text.isdigit() else text.lower() for text in re.split('([0-9]+)', str(s))] +# --- NATIVE WINDOWS ICON HANDLING --- +if os.name == 'nt': + class SHFILEINFO(ctypes.Structure): + _fields_ = [ + ("hIcon", wintypes.HANDLE), + ("iIcon", ctypes.c_int), + ("dwAttributes", wintypes.DWORD), + ("szDisplayName", wintypes.WCHAR * 260), + ("szTypeName", wintypes.WCHAR * 80), + ] + SHGFI_ICON = 0x000000100 + SHGFI_SMALLICON = 0x000000001 + SHGFI_USEFILEATTRIBUTES = 0x000000010 + class UploadDropTarget(wx.FileDropTarget): def __init__(self, window, app): wx.FileDropTarget.__init__(self) @@ -444,6 +460,8 @@ class SharePointApp(wx.Frame): self.sort_col = 0 # Default (Navn) self.sort_asc = True self.compact_mode = False + self.ext_icons = {} # Mapping fra .ext -> index i image_list + self.current_web_url = None # URL til nuværende lokation i browser # System Ikoner self.image_list = wx.ImageList(16, 16) @@ -714,6 +732,13 @@ class SharePointApp(wx.Frame): delete_item = menu.Append(wx.ID_ANY, f"{self.get_txt('msg_delete')} '{item['name']}'") delete_item.SetBitmap(wx.ArtProvider.GetBitmap(wx.ART_DELETE, wx.ART_MENU, (16, 16))) self.Bind(wx.EVT_MENU, lambda e: self.on_delete_items_clicked(selected_items), delete_item) + + # Åbn i browser + if item.get('web_url'): + menu.AppendSeparator() + web_item = menu.Append(wx.ID_ANY, self.get_txt("msg_open_browser")) + web_item.SetBitmap(wx.ArtProvider.GetBitmap(wx.ART_GO_FORWARD, wx.ART_MENU, (16, 16))) + self.Bind(wx.EVT_MENU, lambda e, url=item['web_url']: webbrowser.open(url), web_item) else: # Flere emner valgt delete_items = menu.Append(wx.ID_ANY, f"{self.get_txt('msg_delete')} {len(selected_indices)} " + ("emner" if self.lang == "da" else "items")) @@ -721,6 +746,12 @@ class SharePointApp(wx.Frame): self.Bind(wx.EVT_MENU, lambda e: self.on_delete_items_clicked(selected_items), delete_items) else: # Menu for selve mappen (hvis man trykker på det tomme felt) + if self.current_web_url: + web_item = menu.Append(wx.ID_ANY, self.get_txt("msg_open_browser")) + web_item.SetBitmap(wx.ArtProvider.GetBitmap(wx.ART_GO_FORWARD, wx.ART_MENU, (16, 16))) + self.Bind(wx.EVT_MENU, lambda e: webbrowser.open(self.current_web_url), web_item) + menu.AppendSeparator() + if self.current_drive_id: upload_item = menu.Append(wx.ID_ANY, self.get_txt("msg_upload_here")) upload_item.SetBitmap(wx.ArtProvider.GetBitmap(wx.ART_FILE_OPEN, wx.ART_MENU, (16, 16))) @@ -757,6 +788,13 @@ class SharePointApp(wx.Frame): fav_item = menu.Append(wx.ID_ANY, self.get_txt("btn_add_fav")) fav_item.SetBitmap(wx.ArtProvider.GetBitmap(wx.ART_ADD_BOOKMARK, wx.ART_MENU, (16, 16))) self.Bind(wx.EVT_MENU, lambda e, d=data: self.add_favorite(d), fav_item) + + if data.get('web_url'): + menu.AppendSeparator() + web_item = menu.Append(wx.ID_ANY, self.get_txt("msg_open_browser")) + web_item.SetBitmap(wx.ArtProvider.GetBitmap(wx.ART_GO_FORWARD, wx.ART_MENU, (16, 16))) + self.Bind(wx.EVT_MENU, lambda e, url=data['web_url']: webbrowser.open(url), web_item) + menu.AppendSeparator() refresh_item = menu.Append(wx.ID_ANY, self.get_txt("btn_refresh")) @@ -779,7 +817,8 @@ class SharePointApp(wx.Frame): "type": item['type'], "drive_id": item.get('drive_id'), "site_id": item.get('id') if item['type'] == "SITE" else self.current_site_id, - "path": self.current_path + [item['name']] + "path": self.current_path + [item['name']], + "web_url": item.get('web_url') } self.favorites.append(new_fav) self.save_favorites() @@ -836,6 +875,13 @@ class SharePointApp(wx.Frame): fav = self.favorites[idx] menu = wx.Menu() + + if fav.get('web_url'): + web_item = menu.Append(wx.ID_ANY, self.get_txt("msg_open_browser")) + web_item.SetBitmap(wx.ArtProvider.GetBitmap(wx.ART_GO_FORWARD, wx.ART_MENU, (16, 16))) + self.Bind(wx.EVT_MENU, lambda e: webbrowser.open(fav['web_url']), web_item) + menu.AppendSeparator() + remove_item = menu.Append(wx.ID_ANY, self.get_txt("btn_remove_fav")) remove_item.SetBitmap(wx.ArtProvider.GetBitmap(wx.ART_DEL_BOOKMARK, wx.ART_MENU, (16, 16))) self.Bind(wx.EVT_MENU, lambda e: self.remove_favorite(fav['id']), remove_item) @@ -1290,7 +1336,7 @@ class SharePointApp(wx.Frame): if not self: return try: self.path_sizer.Clear(True) - self._add_path_segment("📍 " + self.get_txt("title"), "ROOT") + self._add_path_segment(self.get_txt("title"), "ROOT") # Vis stien fra self.current_path path_segments = self.current_path[1:] if self.current_path and self.current_path[0] == "SharePoint" else self.current_path @@ -1326,6 +1372,8 @@ class SharePointApp(wx.Frame): btn.SetFont(wx.Font(9, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) if node == "ROOT": + btn.SetBitmap(wx.ArtProvider.GetBitmap(wx.ART_GO_HOME, wx.ART_CMN_DIALOG, (16, 16))) + btn.SetBitmapMargins((4, 0)) btn.Bind(wx.EVT_BUTTON, self.load_sites) elif node: btn.Bind(wx.EVT_BUTTON, lambda e: self.tree_ctrl.SelectItem(node)) @@ -1408,7 +1456,8 @@ class SharePointApp(wx.Frame): node = self.tree_ctrl.AppendItem(self.tree_root, name, image=self.idx_site) self.tree_item_data[node] = { "type": "SITE", "id": site['id'], "name": name, - "drive_id": None, "path": ["SharePoint", name], "loaded": False + "drive_id": None, "path": ["SharePoint", name], "loaded": False, + "web_url": site.get('webUrl') } self.tree_ctrl.SetItemHasChildren(node, True) @@ -1472,7 +1521,8 @@ class SharePointApp(wx.Frame): node = self.tree_ctrl.AppendItem(parent_node, name, image=self.idx_drive) self.tree_item_data[node] = { "type": "DRIVE", "id": drive_id, "name": name, - "drive_id": drive_id, "path": parent_data["path"] + [name], "loaded": False + "drive_id": drive_id, "path": parent_data["path"] + [name], "loaded": False, + "web_url": drive.get('webUrl') } self.tree_ctrl.SetItemHasChildren(node, True) if drive_id == getattr(self, '_pending_tree_selection_id', None): @@ -1493,7 +1543,8 @@ class SharePointApp(wx.Frame): node = self.tree_ctrl.AppendItem(parent_node, name, image=self.idx_folder) self.tree_item_data[node] = { "type": "FOLDER", "id": folder_id, "name": name, - "drive_id": parent_data["drive_id"], "path": parent_data["path"] + [name], "loaded": False + "drive_id": parent_data["drive_id"], "path": parent_data["path"] + [name], "loaded": False, + "web_url": folder.get('webUrl') } self.tree_ctrl.SetItemHasChildren(node, True) if folder_id == getattr(self, '_pending_tree_selection_id', None): @@ -1603,6 +1654,73 @@ class SharePointApp(wx.Frame): self.current_items.sort(key=sort_logic, reverse=not self.sort_asc) self._update_list_view_only() + def get_icon_idx_for_file(self, filename): + ext = os.path.splitext(filename)[1].lower() + if not ext or ext == ".": + return self.idx_file + + if ext in self.ext_icons: + return self.ext_icons[ext] + + # Prøv native Windows Shell API (SHGetFileInfo) + if os.name == 'nt': + try: + # Sæt argtypes så vi er sikre på typerne + shell32 = ctypes.windll.shell32 + shell32.SHGetFileInfoW.argtypes = [wintypes.LPCWSTR, wintypes.DWORD, ctypes.POINTER(SHFILEINFO), wintypes.UINT, wintypes.UINT] + shell32.SHGetFileInfoW.restype = wintypes.DWORD + + sfi = SHFILEINFO() + # Brug et dummy-filnavn fremfor blot endelsen (sikrer bedre match på tværs af Windows versioner) + dummy_file = "C:\\file" + ext + # Eksplicit unicode buffer for at undgå konverteringsfejl + path_buf = ctypes.create_unicode_buffer(dummy_file) + res = shell32.SHGetFileInfoW(path_buf, 0x80, ctypes.byref(sfi), ctypes.sizeof(sfi), SHGFI_ICON | SHGFI_SMALLICON | SHGFI_USEFILEATTRIBUTES) + + if res and sfi.hIcon: + # Mest kompatible måde at få en bitmap fra HICON i wxPython + bmp = wx.Bitmap.FromHICON(sfi.hIcon) + if bmp.IsOk(): + # Sørg for at den er 16x16 + if bmp.GetWidth() != 16 or bmp.GetHeight() != 16: + img = bmp.ConvertToImage() + bmp = wx.Bitmap(img.Scale(16, 16, wx.IMAGE_QUALITY_HIGH)) + + idx = self.image_list.Add(bmp) + # Ryd op i handle med det samme + ctypes.windll.user32.DestroyIcon(sfi.hIcon) + self.ext_icons[ext] = idx + return idx + ctypes.windll.user32.DestroyIcon(sfi.hIcon) + except Exception as e: + pass + + # Gammelt fallback til MimeTypesManager (hvis SHGetFileInfo fejler) + try: + with wx.LogNull(): + ft = wx.TheMimeTypesManager.GetFileTypeFromExtension(ext[1:] if ext.startswith('.') else ext) + if ft: + info = ft.GetIconInfo() + if info: + icon = info[0] if isinstance(info, tuple) else info.GetIcon() + if icon and icon.IsOk(): + bmp = wx.Bitmap(icon) + if bmp.GetWidth() != 16 or bmp.GetHeight() != 16: + img = bmp.ConvertToImage() + bmp = wx.Bitmap(img.Scale(16, 16, wx.IMAGE_QUALITY_HIGH)) + idx = self.image_list.Add(bmp) + self.ext_icons[ext] = idx + return idx + except: + pass + + self.ext_icons[ext] = self.idx_file + return self.idx_file + + # Fallback + self.ext_icons[ext_clean] = self.idx_file + return self.idx_file + def _update_list_view_only(self): self.list_ctrl.DeleteAllItems() for i, item in enumerate(self.current_items): @@ -1610,6 +1728,8 @@ class SharePointApp(wx.Frame): 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 + elif item['type'] == "FILE": + img_idx = self.get_icon_idx_for_file(item['name']) 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") @@ -1635,7 +1755,8 @@ class SharePointApp(wx.Frame): # Anvend sortering før visning self.apply_sorting() - self.set_status(self.get_txt("status_ready")) + # Opdater tilstand + self.current_web_url = parent_data.get('web_url') if parent_data['type'] == "SITE": self.current_site_id = parent_data['id']