Compare commits
4 Commits
784ca755d5
...
2dd31d19c6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2dd31d19c6 | ||
|
|
6b6387f5c8 | ||
|
|
6ab9901cc8 | ||
|
|
c5f18b520a |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -41,6 +41,7 @@ coverage.xml
|
|||||||
|
|
||||||
# Local settings (keep your secrets safe)
|
# Local settings (keep your secrets safe)
|
||||||
settings.json
|
settings.json
|
||||||
|
searchindex.json
|
||||||
|
|
||||||
# Temporary files
|
# Temporary files
|
||||||
C:\Temp_SP\
|
C:\Temp_SP\
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ import msal
|
|||||||
import wx
|
import wx
|
||||||
import wx.lib.newevent
|
import wx.lib.newevent
|
||||||
import webbrowser
|
import webbrowser
|
||||||
|
import re
|
||||||
|
import ctypes
|
||||||
|
from ctypes import wintypes
|
||||||
|
|
||||||
# --- STIHÅNDTERING (Til EXE-brug) ---
|
# --- STIHÅNDTERING (Til EXE-brug) ---
|
||||||
if getattr(sys, 'frozen', False):
|
if getattr(sys, 'frozen', False):
|
||||||
@@ -21,6 +24,7 @@ else:
|
|||||||
CONFIG_DIR = RESOURCE_DIR
|
CONFIG_DIR = RESOURCE_DIR
|
||||||
|
|
||||||
SETTINGS_FILE = os.path.join(CONFIG_DIR, 'settings.json')
|
SETTINGS_FILE = os.path.join(CONFIG_DIR, 'settings.json')
|
||||||
|
SEARCH_INDEX_FILE = os.path.join(CONFIG_DIR, 'searchindex.json')
|
||||||
|
|
||||||
def load_settings():
|
def load_settings():
|
||||||
default_settings = {
|
default_settings = {
|
||||||
@@ -30,7 +34,8 @@ def load_settings():
|
|||||||
"language": "da", # da eller en
|
"language": "da", # da eller en
|
||||||
"favorites": [], # Liste over {id, name, type, drive_id, site_id, path}
|
"favorites": [], # Liste over {id, name, type, drive_id, site_id, path}
|
||||||
"fav_visible": True,
|
"fav_visible": True,
|
||||||
"license_key": ""
|
"license_key": "",
|
||||||
|
"enable_global_search": False
|
||||||
}
|
}
|
||||||
if not os.path.exists(SETTINGS_FILE):
|
if not os.path.exists(SETTINGS_FILE):
|
||||||
with open(SETTINGS_FILE, 'w', encoding='utf-8') as f:
|
with open(SETTINGS_FILE, 'w', encoding='utf-8') as f:
|
||||||
@@ -147,7 +152,15 @@ STRINGS = {
|
|||||||
"msg_fav_exists": "'{name}' er allerede i favoritter.",
|
"msg_fav_exists": "'{name}' er allerede i favoritter.",
|
||||||
"settings_license_group": "Licens / Aktivering",
|
"settings_license_group": "Licens / Aktivering",
|
||||||
"settings_license_key": "Licensnøgle:",
|
"settings_license_key": "Licensnøgle:",
|
||||||
"settings_license_status": "Status: Ikke aktiveret"
|
"settings_license_status": "Status: Ikke aktiveret",
|
||||||
|
"settings_search_group": "Global Søgning & Indeksering",
|
||||||
|
"settings_search_enable": "Aktiver global søgning (Advarsel: Kan tage lang tid og bruge mange ressourcer/API kald)",
|
||||||
|
"btn_global_search": "Søg i alt",
|
||||||
|
"status_indexing": "Indekserer... ({count} fundet)",
|
||||||
|
"status_indexing_done": "Indeksering færdig. {count} filer klar.",
|
||||||
|
"col_path": "Sti",
|
||||||
|
"msg_search_results": "Søgeresultater for '{query}'",
|
||||||
|
"msg_search_placeholder": "Søg..."
|
||||||
},
|
},
|
||||||
"en": {
|
"en": {
|
||||||
"title": "SharePoint Explorer",
|
"title": "SharePoint Explorer",
|
||||||
@@ -239,13 +252,39 @@ STRINGS = {
|
|||||||
"msg_fav_exists": "'{name}' is already in favorites.",
|
"msg_fav_exists": "'{name}' is already in favorites.",
|
||||||
"settings_license_group": "License / Activation",
|
"settings_license_group": "License / Activation",
|
||||||
"settings_license_key": "License Key:",
|
"settings_license_key": "License Key:",
|
||||||
"settings_license_status": "Status: Not activated"
|
"settings_license_status": "Status: Not activated",
|
||||||
|
"settings_search_group": "Global Search & Indexing",
|
||||||
|
"settings_search_enable": "Enable global search (Warning: indexing can be slow and resource-heavy)",
|
||||||
|
"btn_global_search": "Global Search",
|
||||||
|
"status_indexing": "Indexing... ({count} found)",
|
||||||
|
"status_indexing_done": "Indexing done. {count} files ready.",
|
||||||
|
"col_path": "Path",
|
||||||
|
"msg_search_results": "Search results for '{query}'",
|
||||||
|
"msg_search_placeholder": "Search..."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if not os.path.exists(TEMP_DIR):
|
if not os.path.exists(TEMP_DIR):
|
||||||
os.makedirs(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))]
|
||||||
|
|
||||||
|
# --- 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):
|
class UploadDropTarget(wx.FileDropTarget):
|
||||||
def __init__(self, window, app):
|
def __init__(self, window, app):
|
||||||
wx.FileDropTarget.__init__(self)
|
wx.FileDropTarget.__init__(self)
|
||||||
@@ -377,6 +416,16 @@ class SettingsDialog(wx.Dialog):
|
|||||||
|
|
||||||
inner_vbox.Add(lang_sizer, 0, wx.EXPAND | wx.ALL, 10)
|
inner_vbox.Add(lang_sizer, 0, wx.EXPAND | wx.ALL, 10)
|
||||||
|
|
||||||
|
# --- Group: Global Search ---
|
||||||
|
search_box = wx.StaticBox(panel, label=self.get_txt("settings_search_group"))
|
||||||
|
search_sizer = wx.StaticBoxSizer(search_box, wx.VERTICAL)
|
||||||
|
|
||||||
|
self.enable_search_cb = wx.CheckBox(panel, label=self.get_txt("settings_search_enable"))
|
||||||
|
self.enable_search_cb.SetValue(self.settings.get("enable_global_search", False))
|
||||||
|
search_sizer.Add(self.enable_search_cb, 0, wx.ALL, 5)
|
||||||
|
|
||||||
|
inner_vbox.Add(search_sizer, 0, wx.EXPAND | wx.ALL, 10)
|
||||||
|
|
||||||
panel.SetSizer(inner_vbox)
|
panel.SetSizer(inner_vbox)
|
||||||
inner_vbox.Fit(panel)
|
inner_vbox.Fit(panel)
|
||||||
vbox.Add(panel, 1, wx.EXPAND | wx.ALL, 0)
|
vbox.Add(panel, 1, wx.EXPAND | wx.ALL, 0)
|
||||||
@@ -406,6 +455,7 @@ class SettingsDialog(wx.Dialog):
|
|||||||
self.settings["temp_dir"] = self.temp_dir_picker.GetPath()
|
self.settings["temp_dir"] = self.temp_dir_picker.GetPath()
|
||||||
self.settings["language"] = "da" if self.lang_choice.GetSelection() == 0 else "en"
|
self.settings["language"] = "da" if self.lang_choice.GetSelection() == 0 else "en"
|
||||||
self.settings["license_key"] = self.license_ctrl.GetValue().strip()
|
self.settings["license_key"] = self.license_ctrl.GetValue().strip()
|
||||||
|
self.settings["enable_global_search"] = self.enable_search_cb.GetValue()
|
||||||
|
|
||||||
if not self.settings["client_id"] or not self.settings["tenant_id"]:
|
if not self.settings["client_id"] or not self.settings["tenant_id"]:
|
||||||
wx.MessageBox("Client ID og Tenant ID skal udfyldes.", "Fejl", wx.OK | wx.ICON_ERROR)
|
wx.MessageBox("Client ID og Tenant ID skal udfyldes.", "Fejl", wx.OK | wx.ICON_ERROR)
|
||||||
@@ -436,14 +486,34 @@ class SharePointApp(wx.Frame):
|
|||||||
self.active_edits = {} # item_id -> { "name": name, "event": Event, "waiting": bool }
|
self.active_edits = {} # item_id -> { "name": name, "event": Event, "waiting": bool }
|
||||||
self.favorites = settings.get("favorites", [])
|
self.favorites = settings.get("favorites", [])
|
||||||
self.fav_visible = settings.get("fav_visible", True)
|
self.fav_visible = settings.get("fav_visible", True)
|
||||||
|
self.enable_global_search = settings.get("enable_global_search", False)
|
||||||
|
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
|
||||||
|
self.search_text = ""
|
||||||
|
self.search_index = {} # site_id -> { item_id -> item_data }
|
||||||
|
self.is_searching_globally = False
|
||||||
|
|
||||||
# System Ikoner (Brug ART_CMN_DIALOG eller ART_TOOLBAR for mere farve på Windows)
|
self.load_search_index()
|
||||||
|
|
||||||
|
# System Ikoner
|
||||||
self.image_list = wx.ImageList(16, 16)
|
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
|
def add_icon(art_id, client=wx.ART_CMN_DIALOG):
|
||||||
self.idx_folder = self.image_list.Add(wx.ArtProvider.GetBitmap(wx.ART_FOLDER, wx.ART_CMN_DIALOG, (16, 16))) # Folder
|
bmp = wx.ArtProvider.GetBitmap(art_id, client, (16, 16))
|
||||||
self.idx_file = self.image_list.Add(wx.ArtProvider.GetBitmap(wx.ART_NORMAL_FILE, wx.ART_CMN_DIALOG, (16, 16))) # File
|
if not bmp.IsOk():
|
||||||
self.idx_star = self.image_list.Add(wx.ArtProvider.GetBitmap(wx.ART_ADD_BOOKMARK, wx.ART_CMN_DIALOG, (16, 16))) # Favorit stjerne
|
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
|
# Threading/Sync til filredigering
|
||||||
|
|
||||||
@@ -547,6 +617,22 @@ class SharePointApp(wx.Frame):
|
|||||||
|
|
||||||
nav_hbox.AddStretchSpacer(1)
|
nav_hbox.AddStretchSpacer(1)
|
||||||
|
|
||||||
|
# SØGEFELT
|
||||||
|
self.search_ctrl = wx.SearchCtrl(nav_panel, size=(200, 25), style=wx.TE_PROCESS_ENTER)
|
||||||
|
self.search_ctrl.ShowCancelButton(True)
|
||||||
|
self.search_ctrl.SetDescriptiveText(self.get_txt("msg_search_placeholder"))
|
||||||
|
self.search_ctrl.Bind(wx.EVT_TEXT, self.on_search_text)
|
||||||
|
self.search_ctrl.Bind(wx.EVT_SEARCHCTRL_CANCEL_BTN, self.on_search_cancel)
|
||||||
|
self.search_ctrl.Bind(wx.EVT_TEXT_ENTER, self.on_global_search)
|
||||||
|
nav_hbox.Add(self.search_ctrl, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 5)
|
||||||
|
|
||||||
|
self.global_search_btn = wx.Button(nav_panel, label="", size=(30, 30))
|
||||||
|
self.global_search_btn.SetBitmap(wx.ArtProvider.GetBitmap(wx.ART_FIND, wx.ART_BUTTON, (16, 16)))
|
||||||
|
self.global_search_btn.SetToolTip(self.get_txt("btn_global_search"))
|
||||||
|
self.global_search_btn.Bind(wx.EVT_BUTTON, self.on_global_search)
|
||||||
|
self.global_search_btn.Show(self.enable_global_search)
|
||||||
|
nav_hbox.Add(self.global_search_btn, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 10)
|
||||||
|
|
||||||
self.login_btn = wx.Button(nav_panel, label=self.get_txt("btn_login"), size=(130, 30))
|
self.login_btn = wx.Button(nav_panel, label=self.get_txt("btn_login"), size=(130, 30))
|
||||||
self.login_btn.SetBackgroundColour(wx.Colour(40, 167, 69)) # Grøn
|
self.login_btn.SetBackgroundColour(wx.Colour(40, 167, 69)) # Grøn
|
||||||
self.login_btn.SetForegroundColour(wx.WHITE)
|
self.login_btn.SetForegroundColour(wx.WHITE)
|
||||||
@@ -564,6 +650,8 @@ class SharePointApp(wx.Frame):
|
|||||||
|
|
||||||
nav_panel.SetSizer(nav_hbox)
|
nav_panel.SetSizer(nav_hbox)
|
||||||
vbox.Add(nav_panel, 0, wx.EXPAND | wx.ALL, 5)
|
vbox.Add(nav_panel, 0, wx.EXPAND | wx.ALL, 5)
|
||||||
|
self.nav_hbox = nav_hbox # Gem til resize
|
||||||
|
self.nav_panel = nav_panel
|
||||||
|
|
||||||
# 2. PATH BREADCRUMBS (Adresselinje-stil)
|
# 2. PATH BREADCRUMBS (Adresselinje-stil)
|
||||||
self.path_panel = wx.Panel(panel, style=wx.BORDER_SIMPLE)
|
self.path_panel = wx.Panel(panel, style=wx.BORDER_SIMPLE)
|
||||||
@@ -636,8 +724,10 @@ class SharePointApp(wx.Frame):
|
|||||||
self.list_ctrl.InsertColumn(1, self.get_txt("col_type"), width=120)
|
self.list_ctrl.InsertColumn(1, self.get_txt("col_type"), width=120)
|
||||||
self.list_ctrl.InsertColumn(2, self.get_txt("col_size"), width=80)
|
self.list_ctrl.InsertColumn(2, self.get_txt("col_size"), width=80)
|
||||||
self.list_ctrl.InsertColumn(3, self.get_txt("col_modified"), width=180)
|
self.list_ctrl.InsertColumn(3, self.get_txt("col_modified"), width=180)
|
||||||
|
self.list_ctrl.InsertColumn(4, self.get_txt("col_path"), width=0) # Skjult som standard
|
||||||
|
|
||||||
self.list_ctrl.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.on_item_activated)
|
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)
|
self.list_ctrl.Bind(wx.EVT_CONTEXT_MENU, self.on_right_click)
|
||||||
|
|
||||||
# AKTIVER DRAG & DROP
|
# AKTIVER DRAG & DROP
|
||||||
@@ -656,6 +746,7 @@ class SharePointApp(wx.Frame):
|
|||||||
self.status_bar.SetStatusText(self.get_txt("status_ready"))
|
self.status_bar.SetStatusText(self.get_txt("status_ready"))
|
||||||
|
|
||||||
panel.SetSizer(vbox)
|
panel.SetSizer(vbox)
|
||||||
|
self.Bind(wx.EVT_SIZE, self.on_resize)
|
||||||
self.Layout()
|
self.Layout()
|
||||||
|
|
||||||
def on_right_click(self, event):
|
def on_right_click(self, event):
|
||||||
@@ -693,6 +784,13 @@ class SharePointApp(wx.Frame):
|
|||||||
delete_item = menu.Append(wx.ID_ANY, f"{self.get_txt('msg_delete')} '{item['name']}'")
|
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)))
|
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)
|
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:
|
else:
|
||||||
# Flere emner valgt
|
# 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"))
|
delete_items = menu.Append(wx.ID_ANY, f"{self.get_txt('msg_delete')} {len(selected_indices)} " + ("emner" if self.lang == "da" else "items"))
|
||||||
@@ -700,6 +798,12 @@ class SharePointApp(wx.Frame):
|
|||||||
self.Bind(wx.EVT_MENU, lambda e: self.on_delete_items_clicked(selected_items), delete_items)
|
self.Bind(wx.EVT_MENU, lambda e: self.on_delete_items_clicked(selected_items), delete_items)
|
||||||
else:
|
else:
|
||||||
# Menu for selve mappen (hvis man trykker på det tomme felt)
|
# 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:
|
if self.current_drive_id:
|
||||||
upload_item = menu.Append(wx.ID_ANY, self.get_txt("msg_upload_here"))
|
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)))
|
upload_item.SetBitmap(wx.ArtProvider.GetBitmap(wx.ART_FILE_OPEN, wx.ART_MENU, (16, 16)))
|
||||||
@@ -736,6 +840,13 @@ class SharePointApp(wx.Frame):
|
|||||||
fav_item = menu.Append(wx.ID_ANY, self.get_txt("btn_add_fav"))
|
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)))
|
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)
|
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()
|
menu.AppendSeparator()
|
||||||
|
|
||||||
refresh_item = menu.Append(wx.ID_ANY, self.get_txt("btn_refresh"))
|
refresh_item = menu.Append(wx.ID_ANY, self.get_txt("btn_refresh"))
|
||||||
@@ -758,7 +869,8 @@ class SharePointApp(wx.Frame):
|
|||||||
"type": item['type'],
|
"type": item['type'],
|
||||||
"drive_id": item.get('drive_id'),
|
"drive_id": item.get('drive_id'),
|
||||||
"site_id": item.get('id') if item['type'] == "SITE" else self.current_site_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.favorites.append(new_fav)
|
||||||
self.save_favorites()
|
self.save_favorites()
|
||||||
@@ -815,6 +927,13 @@ class SharePointApp(wx.Frame):
|
|||||||
fav = self.favorites[idx]
|
fav = self.favorites[idx]
|
||||||
|
|
||||||
menu = wx.Menu()
|
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 = 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)))
|
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)
|
self.Bind(wx.EVT_MENU, lambda e: self.remove_favorite(fav['id']), remove_item)
|
||||||
@@ -1118,24 +1237,27 @@ class SharePointApp(wx.Frame):
|
|||||||
|
|
||||||
self.show_info(self.get_txt("msg_settings_saved"))
|
self.show_info(self.get_txt("msg_settings_saved"))
|
||||||
|
|
||||||
|
# Opdater Global Search visning
|
||||||
|
self.enable_global_search = settings.get("enable_global_search", False)
|
||||||
|
self.global_search_btn.Show(self.enable_global_search)
|
||||||
|
self.nav_panel.Layout()
|
||||||
|
|
||||||
dlg.Destroy()
|
dlg.Destroy()
|
||||||
|
|
||||||
def refresh_ui_texts(self):
|
def refresh_ui_texts(self):
|
||||||
# Update UI texts for main frame and buttons
|
# Update UI texts for main frame and buttons
|
||||||
self.SetTitle(self.get_txt("title"))
|
self.SetTitle(self.get_txt("title"))
|
||||||
self.back_btn.SetLabel(self.get_txt("btn_back"))
|
|
||||||
self.home_btn.SetLabel(self.get_txt("btn_home"))
|
# Hvis vi er i compact mode, skal vi ikke sætte labels på knapperne nu
|
||||||
self.done_btn.SetLabel(self.get_txt("btn_save_changes"))
|
if not self.compact_mode:
|
||||||
self.upload_btn.SetLabel(self.get_txt("btn_upload_file"))
|
self._update_button_labels(full=True)
|
||||||
self.upload_folder_btn.SetLabel(self.get_txt("btn_upload_folder"))
|
|
||||||
self.new_folder_btn.SetLabel(self.get_txt("btn_new_folder"))
|
self.settings_btn.SetLabel(self.get_txt("btn_settings") if not self.compact_mode else "")
|
||||||
self.refresh_btn.SetLabel(self.get_txt("btn_refresh"))
|
|
||||||
self.settings_btn.SetLabel(self.get_txt("btn_settings"))
|
|
||||||
|
|
||||||
if self.access_token:
|
if self.access_token:
|
||||||
self.login_btn.SetLabel(self.get_txt("btn_logged_in"))
|
self.login_btn.SetLabel(self.get_txt("btn_logged_in") if not self.compact_mode else "")
|
||||||
else:
|
else:
|
||||||
self.login_btn.SetLabel(self.get_txt("btn_login"))
|
self.login_btn.SetLabel(self.get_txt("btn_login") if not self.compact_mode else "")
|
||||||
|
|
||||||
# Re-set headers for ListCtrl
|
# Re-set headers for ListCtrl
|
||||||
cols = [self.get_txt("col_name"), self.get_txt("col_type"), self.get_txt("col_size"), self.get_txt("col_modified")]
|
cols = [self.get_txt("col_name"), self.get_txt("col_type"), self.get_txt("col_size"), self.get_txt("col_modified")]
|
||||||
@@ -1146,6 +1268,7 @@ class SharePointApp(wx.Frame):
|
|||||||
self.list_ctrl.SetColumn(i, info)
|
self.list_ctrl.SetColumn(i, info)
|
||||||
|
|
||||||
self.set_status(self.get_txt("status_ready"))
|
self.set_status(self.get_txt("status_ready"))
|
||||||
|
self.search_ctrl.SetDescriptiveText(self.get_txt("msg_search_placeholder"))
|
||||||
self._refresh_current_view()
|
self._refresh_current_view()
|
||||||
|
|
||||||
def on_language_changed(self, event):
|
def on_language_changed(self, event):
|
||||||
@@ -1201,11 +1324,77 @@ class SharePointApp(wx.Frame):
|
|||||||
self.current_items = []
|
self.current_items = []
|
||||||
self.update_path_display()
|
self.update_path_display()
|
||||||
|
|
||||||
|
def on_resize(self, event):
|
||||||
|
width = self.GetSize().width
|
||||||
|
threshold = 1100
|
||||||
|
|
||||||
|
if width < threshold and not self.compact_mode:
|
||||||
|
self.compact_mode = True
|
||||||
|
self._update_button_labels(full=False)
|
||||||
|
elif width >= threshold and self.compact_mode:
|
||||||
|
self.compact_mode = False
|
||||||
|
self._update_button_labels(full=True)
|
||||||
|
|
||||||
|
event.Skip()
|
||||||
|
|
||||||
|
def _update_button_labels(self, full=True):
|
||||||
|
if not self: return
|
||||||
|
try:
|
||||||
|
# Liste over knapper og deres tilhørende oversættelses-nøgle
|
||||||
|
btns = [
|
||||||
|
(self.back_btn, "btn_back", 40, 110),
|
||||||
|
(self.home_btn, "btn_home", 40, 110),
|
||||||
|
(self.refresh_btn, "btn_refresh", 40, 110),
|
||||||
|
(self.upload_btn, "btn_upload_file", 40, 130),
|
||||||
|
(self.upload_folder_btn, "btn_upload_folder", 40, 130),
|
||||||
|
(self.new_folder_btn, "btn_new_folder", 40, 120),
|
||||||
|
(self.settings_btn, "btn_settings", 40, 130)
|
||||||
|
]
|
||||||
|
|
||||||
|
for btn, key, compact_w, full_w in btns:
|
||||||
|
txt = self.get_txt(key)
|
||||||
|
btn.SetLabel(txt if full else "")
|
||||||
|
btn.SetToolTip(txt)
|
||||||
|
btn.SetMinSize((compact_w if not full else full_w, 30))
|
||||||
|
btn.SetSize((compact_w if not full else full_w, 30))
|
||||||
|
btn.SetBitmapMargins((12 if full else 10, 0))
|
||||||
|
|
||||||
|
# Special cases for Login and Done buttons
|
||||||
|
if full:
|
||||||
|
login_txt = self.get_txt("btn_logged_in") if self.access_token else self.get_txt("btn_login")
|
||||||
|
self.login_btn.SetLabel(login_txt)
|
||||||
|
self.login_btn.SetToolTip(login_txt)
|
||||||
|
self.login_btn.SetMinSize((130, 30))
|
||||||
|
self.login_btn.SetBitmapMargins((12, 0))
|
||||||
|
|
||||||
|
done_txt = self.get_txt("btn_save_changes")
|
||||||
|
self.done_btn.SetLabel(done_txt)
|
||||||
|
self.done_btn.SetToolTip(done_txt)
|
||||||
|
self.done_btn.SetMinSize((250, 30))
|
||||||
|
self.done_btn.SetBitmapMargins((12, 0))
|
||||||
|
else:
|
||||||
|
login_txt = self.get_txt("btn_logged_in") if self.access_token else self.get_txt("btn_login")
|
||||||
|
self.login_btn.SetLabel("")
|
||||||
|
self.login_btn.SetToolTip(login_txt)
|
||||||
|
self.login_btn.SetMinSize((40, 30))
|
||||||
|
self.login_btn.SetBitmapMargins((10, 0))
|
||||||
|
|
||||||
|
done_txt = self.get_txt("btn_save_changes")
|
||||||
|
self.done_btn.SetLabel("")
|
||||||
|
self.done_btn.SetToolTip(done_txt)
|
||||||
|
self.done_btn.SetMinSize((40, 30))
|
||||||
|
self.done_btn.SetBitmapMargins((10, 0))
|
||||||
|
|
||||||
|
self.nav_panel.Layout()
|
||||||
|
self.Layout()
|
||||||
|
except RuntimeError:
|
||||||
|
pass
|
||||||
|
|
||||||
def update_path_display(self):
|
def update_path_display(self):
|
||||||
if not self: return
|
if not self: return
|
||||||
try:
|
try:
|
||||||
self.path_sizer.Clear(True)
|
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
|
# 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
|
path_segments = self.current_path[1:] if self.current_path and self.current_path[0] == "SharePoint" else self.current_path
|
||||||
@@ -1241,6 +1430,8 @@ class SharePointApp(wx.Frame):
|
|||||||
btn.SetFont(wx.Font(9, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL))
|
btn.SetFont(wx.Font(9, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL))
|
||||||
|
|
||||||
if node == "ROOT":
|
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)
|
btn.Bind(wx.EVT_BUTTON, self.load_sites)
|
||||||
elif node:
|
elif node:
|
||||||
btn.Bind(wx.EVT_BUTTON, lambda e: self.tree_ctrl.SelectItem(node))
|
btn.Bind(wx.EVT_BUTTON, lambda e: self.tree_ctrl.SelectItem(node))
|
||||||
@@ -1323,7 +1514,8 @@ class SharePointApp(wx.Frame):
|
|||||||
node = self.tree_ctrl.AppendItem(self.tree_root, name, image=self.idx_site)
|
node = self.tree_ctrl.AppendItem(self.tree_root, name, image=self.idx_site)
|
||||||
self.tree_item_data[node] = {
|
self.tree_item_data[node] = {
|
||||||
"type": "SITE", "id": site['id'], "name": name,
|
"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)
|
self.tree_ctrl.SetItemHasChildren(node, True)
|
||||||
|
|
||||||
@@ -1387,7 +1579,8 @@ class SharePointApp(wx.Frame):
|
|||||||
node = self.tree_ctrl.AppendItem(parent_node, name, image=self.idx_drive)
|
node = self.tree_ctrl.AppendItem(parent_node, name, image=self.idx_drive)
|
||||||
self.tree_item_data[node] = {
|
self.tree_item_data[node] = {
|
||||||
"type": "DRIVE", "id": drive_id, "name": name,
|
"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)
|
self.tree_ctrl.SetItemHasChildren(node, True)
|
||||||
if drive_id == getattr(self, '_pending_tree_selection_id', None):
|
if drive_id == getattr(self, '_pending_tree_selection_id', None):
|
||||||
@@ -1408,7 +1601,8 @@ class SharePointApp(wx.Frame):
|
|||||||
node = self.tree_ctrl.AppendItem(parent_node, name, image=self.idx_folder)
|
node = self.tree_ctrl.AppendItem(parent_node, name, image=self.idx_folder)
|
||||||
self.tree_item_data[node] = {
|
self.tree_item_data[node] = {
|
||||||
"type": "FOLDER", "id": folder_id, "name": name,
|
"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)
|
self.tree_ctrl.SetItemHasChildren(node, True)
|
||||||
if folder_id == getattr(self, '_pending_tree_selection_id', None):
|
if folder_id == getattr(self, '_pending_tree_selection_id', None):
|
||||||
@@ -1483,26 +1677,274 @@ class SharePointApp(wx.Frame):
|
|||||||
|
|
||||||
wx.CallAfter(self._populate_list_ctrl, items_data, data)
|
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)
|
||||||
|
|
||||||
|
# Filtrer baseret på søgning
|
||||||
|
display_items = self.current_items
|
||||||
|
if self.search_text:
|
||||||
|
terms = self.search_text.lower().split()
|
||||||
|
display_items = [i for i in self.current_items if all(t in i['name'].lower() for t in terms)]
|
||||||
|
|
||||||
|
self._update_list_view_only(display_items)
|
||||||
|
|
||||||
|
def on_search_text(self, event):
|
||||||
|
self.search_text = self.search_ctrl.GetValue().strip()
|
||||||
|
if self.is_searching_globally and not self.search_text:
|
||||||
|
self.on_search_cancel(None)
|
||||||
|
else:
|
||||||
|
self.apply_sorting()
|
||||||
|
|
||||||
|
def on_search_cancel(self, event):
|
||||||
|
self.search_ctrl.ChangeValue("")
|
||||||
|
self.search_text = ""
|
||||||
|
self.is_searching_globally = False
|
||||||
|
# Vis normale kolonner igen
|
||||||
|
self.list_ctrl.SetColumnWidth(4, 0)
|
||||||
|
self.apply_sorting()
|
||||||
|
|
||||||
|
def on_global_search(self, event):
|
||||||
|
if not self.enable_global_search: return
|
||||||
|
query = self.search_ctrl.GetValue().strip().lower()
|
||||||
|
if not query: return
|
||||||
|
|
||||||
|
terms = query.split()
|
||||||
|
self.search_text = query
|
||||||
|
self.is_searching_globally = True
|
||||||
|
|
||||||
|
results = []
|
||||||
|
# Søg på tværs af alle indekserede sites/drives
|
||||||
|
for site_id, items in self.search_index.items():
|
||||||
|
for item_id, item in items.items():
|
||||||
|
name_lower = item['name'].lower()
|
||||||
|
if all(t in name_lower for t in terms):
|
||||||
|
results.append(item)
|
||||||
|
|
||||||
|
self.current_items = results
|
||||||
|
self.set_status(f"Fandt {len(results)} emner i indekset.")
|
||||||
|
|
||||||
|
# Vis 'Sti' kolonnen
|
||||||
|
self.list_ctrl.SetColumnWidth(4, 300)
|
||||||
|
self._update_list_view_only(results)
|
||||||
|
|
||||||
|
def load_search_index(self):
|
||||||
|
if os.path.exists(SEARCH_INDEX_FILE):
|
||||||
|
try:
|
||||||
|
with open(SEARCH_INDEX_FILE, 'r', encoding='utf-8') as f:
|
||||||
|
self.search_index = json.load(f)
|
||||||
|
except:
|
||||||
|
self.search_index = {}
|
||||||
|
|
||||||
|
def save_search_index(self):
|
||||||
|
try:
|
||||||
|
with open(SEARCH_INDEX_FILE, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(self.search_index, f, indent=4)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def start_indexing(self, site_id, drive_id):
|
||||||
|
if not self.enable_global_search: return
|
||||||
|
threading.Thread(target=self._index_worker, args=(site_id, drive_id), daemon=True).start()
|
||||||
|
|
||||||
|
def _index_worker(self, site_id, drive_id):
|
||||||
|
if not self.ensure_valid_token(): return
|
||||||
|
if site_id not in self.search_index:
|
||||||
|
self.search_index[site_id] = {}
|
||||||
|
|
||||||
|
found_count = 0
|
||||||
|
def crawl(folder_id, current_path_parts):
|
||||||
|
nonlocal found_count
|
||||||
|
if folder_id == "root":
|
||||||
|
url = f"https://graph.microsoft.com/v1.0/drives/{drive_id}/root/children"
|
||||||
|
else:
|
||||||
|
url = f"https://graph.microsoft.com/v1.0/drives/{drive_id}/items/{folder_id}/children"
|
||||||
|
|
||||||
|
res = requests.get(url, headers=self.headers)
|
||||||
|
if res.status_code == 200:
|
||||||
|
items = res.json().get('value', [])
|
||||||
|
for item in items:
|
||||||
|
item_id = item['id']
|
||||||
|
name = item['name']
|
||||||
|
is_folder = 'folder' in item
|
||||||
|
|
||||||
|
full_path = " / ".join(current_path_parts + [name])
|
||||||
|
|
||||||
|
# Gem i indeks
|
||||||
|
data = {
|
||||||
|
"type": "FOLDER" if is_folder else "FILE",
|
||||||
|
"id": item_id,
|
||||||
|
"name": name,
|
||||||
|
"drive_id": drive_id,
|
||||||
|
"site_id": site_id,
|
||||||
|
"modified": item.get('lastModifiedDateTime', '').replace('T', ' ').split('.')[0],
|
||||||
|
"size": item.get('size') if not is_folder else None,
|
||||||
|
"web_url": item.get('webUrl'),
|
||||||
|
"path_display": full_path,
|
||||||
|
"parent_path_parts": current_path_parts
|
||||||
|
}
|
||||||
|
self.search_index[site_id][item_id] = data
|
||||||
|
found_count += 1
|
||||||
|
|
||||||
|
if found_count % 50 == 0:
|
||||||
|
wx.CallAfter(self.set_status, self.get_txt("status_indexing", count=found_count))
|
||||||
|
self.save_search_index() # Gem løbende
|
||||||
|
|
||||||
|
if not self.enable_global_search: return # Stop med det samme
|
||||||
|
if is_folder:
|
||||||
|
crawl(item_id, current_path_parts + [name])
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not self.enable_global_search: return
|
||||||
|
crawl("root", [])
|
||||||
|
self.save_search_index()
|
||||||
|
wx.CallAfter(self.set_status, self.get_txt("status_indexing_done", count=found_count))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
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, items_to_show=None):
|
||||||
|
if items_to_show is None:
|
||||||
|
items_to_show = self.current_items
|
||||||
|
|
||||||
|
self.list_ctrl.DeleteAllItems()
|
||||||
|
for i, item in enumerate(items_to_show):
|
||||||
|
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
|
||||||
|
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")
|
||||||
|
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'])
|
||||||
|
|
||||||
|
if self.is_searching_globally:
|
||||||
|
self.list_ctrl.SetItem(i, 4, item.get('path_display', ""))
|
||||||
|
|
||||||
|
# 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):
|
def _populate_list_ctrl(self, items_data, parent_data):
|
||||||
if not self: return
|
if not self: return
|
||||||
try:
|
try:
|
||||||
self.list_ctrl.DeleteAllItems()
|
# Nulstil søgning ved ny lokation
|
||||||
self.current_items = []
|
wx.CallAfter(lambda: self.search_ctrl.SetValue(""))
|
||||||
for i, item in enumerate(items_data):
|
self.search_text = ""
|
||||||
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)
|
self.current_items = items_data
|
||||||
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.set_status(self.get_txt("status_ready"))
|
# Anvend sortering før visning
|
||||||
|
self.apply_sorting()
|
||||||
|
|
||||||
|
# Opdater tilstand
|
||||||
|
self.current_web_url = parent_data.get('web_url')
|
||||||
|
|
||||||
if parent_data['type'] == "SITE":
|
if parent_data['type'] == "SITE":
|
||||||
self.current_site_id = parent_data['id']
|
self.current_site_id = parent_data['id']
|
||||||
@@ -1513,6 +1955,10 @@ class SharePointApp(wx.Frame):
|
|||||||
self.current_drive_id = parent_data['drive_id']
|
self.current_drive_id = parent_data['drive_id']
|
||||||
self.current_folder_id = parent_data['id']
|
self.current_folder_id = parent_data['id']
|
||||||
|
|
||||||
|
# Start indeksering af dette bibliotek hvis det er nyt
|
||||||
|
if self.current_site_id and self.current_drive_id and not self.is_searching_globally:
|
||||||
|
self.start_indexing(self.current_site_id, self.current_drive_id)
|
||||||
|
|
||||||
# Opdater knap-synlighed
|
# Opdater knap-synlighed
|
||||||
can_upload = self.current_drive_id is not None
|
can_upload = self.current_drive_id is not None
|
||||||
wx.CallAfter(lambda: self._safe_update_buttons(can_upload))
|
wx.CallAfter(lambda: self._safe_update_buttons(can_upload))
|
||||||
@@ -1534,7 +1980,26 @@ class SharePointApp(wx.Frame):
|
|||||||
item = self.current_items[item_idx]
|
item = self.current_items[item_idx]
|
||||||
|
|
||||||
if item['type'] in ["SITE", "DRIVE", "FOLDER"]:
|
if item['type'] in ["SITE", "DRIVE", "FOLDER"]:
|
||||||
self._sync_tree_selection(item['id'])
|
if self.is_searching_globally:
|
||||||
|
# Ved global søgning skal vi "hoppe" til lokationen
|
||||||
|
path_parts = item.get("parent_path_parts", []) + [item['name']]
|
||||||
|
self.current_path = ["SharePoint"] + path_parts
|
||||||
|
item['path'] = self.current_path # Sørg for at baggrundstråden ved hvor vi er
|
||||||
|
|
||||||
|
# Sæt tilstand
|
||||||
|
self.current_site_id = item.get('site_id')
|
||||||
|
self.current_drive_id = item.get('drive_id')
|
||||||
|
if item['type'] == "FOLDER":
|
||||||
|
self.current_folder_id = item['id']
|
||||||
|
elif item['type'] == "DRIVE":
|
||||||
|
self.current_folder_id = "root"
|
||||||
|
|
||||||
|
# Ryd søgning og indlæs indhold
|
||||||
|
self.on_search_cancel(None)
|
||||||
|
self.update_path_display()
|
||||||
|
threading.Thread(target=self._fetch_list_contents_bg, args=(item,), daemon=True).start()
|
||||||
|
else:
|
||||||
|
self._sync_tree_selection(item['id'])
|
||||||
elif item['type'] == "FILE":
|
elif item['type'] == "FILE":
|
||||||
self.open_file(item)
|
self.open_file(item)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user