feat: add native Windows icon support and context menu options to open items in browser
This commit is contained in:
@@ -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']
|
||||
|
||||
Reference in New Issue
Block a user