From 8f80611d329332b86539292c0714f821fa323df2 Mon Sep 17 00:00:00 2001 From: Martin Tranberg Date: Tue, 31 Mar 2026 16:32:26 +0200 Subject: [PATCH] feat: implement settings dialog for configuration of authentication, paths, and language --- sharepoint_browser.py | 224 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 202 insertions(+), 22 deletions(-) diff --git a/sharepoint_browser.py b/sharepoint_browser.py index 4b563ca..7b757b0 100644 --- a/sharepoint_browser.py +++ b/sharepoint_browser.py @@ -122,6 +122,21 @@ STRINGS = { "msg_update_failed_code": "Upload fejlede: {code}", "msg_unknown_error": "Ukendt fejl", "type_unknown": "Ukendt", + "btn_settings": "⚙️ Indstillinger", + "settings_title": "Indstillinger", + "settings_auth_group": "Authentication / API", + "settings_client_id": "App (Client) ID:", + "settings_tenant_id": "Tenant ID:", + "settings_path_group": "Systemstier", + "settings_temp_dir": "Midlertidig mappe:", + "settings_app_path": "Applikationssti:", + "settings_active_temp_path": "Aktuel Temp-sti:", + "settings_lang_group": "Sprog / UI", + "settings_language": "Programsprog:", + "settings_save": "Gem indstillinger", + "settings_cancel": "Annuller", + "msg_settings_saved": "Indstillingerne er gemt.", + "msg_restart_required": "Visse ændringer (f.eks. ID'er) træder først i kraft efter genstart.", "status_login_needed": "Session udløbet. Log ind igen." }, "en": { @@ -192,6 +207,21 @@ STRINGS = { "msg_update_failed_code": "Upload failed: {code}", "msg_unknown_error": "Unknown error", "type_unknown": "Unknown", + "btn_settings": "⚙️ Settings", + "settings_title": "Settings", + "settings_auth_group": "Authentication / API", + "settings_client_id": "App (Client) ID:", + "settings_tenant_id": "Tenant ID:", + "settings_path_group": "System Paths", + "settings_temp_dir": "Temporary folder:", + "settings_app_path": "Application path:", + "settings_active_temp_path": "Active Temp path:", + "settings_lang_group": "Language / UI", + "settings_language": "App Language:", + "settings_save": "Save Settings", + "settings_cancel": "Cancel", + "msg_settings_saved": "Settings saved.", + "msg_restart_required": "Some changes (e.g., IDs) only take effect after restart.", "status_login_needed": "Session expired. Please login again." } } @@ -243,6 +273,117 @@ def format_size(bytes_num): else: return f"{bytes_num/(1024**3):.1f} GB" +def is_configured(cfg): + placeholders = ["DIN_CLIENT_ID_HER", "DIN_TENANT_ID_HER", ""] + return cfg.get("client_id") not in placeholders and cfg.get("tenant_id") not in placeholders + +class SettingsDialog(wx.Dialog): + def __init__(self, parent, current_settings): + lang = current_settings.get("language", "da") + title = STRINGS[lang].get("settings_title", "Settings") + super().__init__(parent, title=title, size=(520, 620)) + + self.settings = current_settings.copy() + self.lang = lang + self.InitUI() + + def get_txt(self, key): + return STRINGS[self.lang].get(key, key) + + def InitUI(self): + vbox = wx.BoxSizer(wx.VERTICAL) + + panel = wx.Panel(self) + inner_vbox = wx.BoxSizer(wx.VERTICAL) + + # --- Group: Authentication --- + auth_box = wx.StaticBox(panel, label=self.get_txt("settings_auth_group")) + auth_sizer = wx.StaticBoxSizer(auth_box, wx.VERTICAL) + + grid = wx.FlexGridSizer(2, 2, 10, 10) + grid.AddGrowableCol(1, 1) + + grid.Add(wx.StaticText(panel, label=self.get_txt("settings_client_id")), 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 5) + self.client_id_ctrl = wx.TextCtrl(panel, value=self.settings.get("client_id", ""), size=(-1, 25)) + grid.Add(self.client_id_ctrl, 1, wx.EXPAND | wx.ALIGN_CENTER_VERTICAL) + + grid.Add(wx.StaticText(panel, label=self.get_txt("settings_tenant_id")), 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 5) + self.tenant_id_ctrl = wx.TextCtrl(panel, value=self.settings.get("tenant_id", ""), size=(-1, 25)) + grid.Add(self.tenant_id_ctrl, 1, wx.EXPAND | wx.ALIGN_CENTER_VERTICAL) + + auth_sizer.Add(grid, 1, wx.EXPAND | wx.ALL, 10) + inner_vbox.Add(auth_sizer, 0, wx.EXPAND | wx.ALL, 10) + + # --- Group: Paths --- + path_box = wx.StaticBox(panel, label=self.get_txt("settings_path_group")) + path_sizer = wx.StaticBoxSizer(path_box, wx.VERTICAL) + + path_sizer.Add(wx.StaticText(panel, label=self.get_txt("settings_temp_dir")), 0, wx.BOTTOM, 5) + self.temp_dir_picker = wx.DirPickerCtrl(panel, path=self.settings.get("temp_dir", "C:\\Temp_SP"), + style=wx.DIRP_DIR_MUST_EXIST) + path_sizer.Add(self.temp_dir_picker, 0, wx.EXPAND | wx.BOTTOM, 10) + + path_sizer.Add(wx.StaticText(panel, label=self.get_txt("settings_app_path")), 0, wx.BOTTOM, 5) + app_path_box = wx.TextCtrl(panel, value=CONFIG_DIR, style=wx.TE_READONLY | wx.BORDER_NONE) + app_path_box.SetBackgroundColour(panel.GetBackgroundColour()) + path_sizer.Add(app_path_box, 0, wx.EXPAND | wx.BOTTOM, 10) + + path_sizer.Add(wx.StaticText(panel, label=self.get_txt("settings_active_temp_path")), 0, wx.BOTTOM, 5) + temp_path_box = wx.TextCtrl(panel, value=TEMP_DIR, style=wx.TE_READONLY | wx.BORDER_NONE) + temp_path_box.SetBackgroundColour(panel.GetBackgroundColour()) + path_sizer.Add(temp_path_box, 0, wx.EXPAND) + + inner_vbox.Add(path_sizer, 0, wx.EXPAND | wx.ALL, 10) + + # --- Group: Language --- + lang_box = wx.StaticBox(panel, label=self.get_txt("settings_lang_group")) + lang_sizer = wx.StaticBoxSizer(lang_box, wx.HORIZONTAL) + + lang_sizer.Add(wx.StaticText(panel, label=self.get_txt("settings_language")), 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 10) + self.lang_choice = wx.Choice(panel, choices=["Dansk", "English"]) + self.lang_choice.SetSelection(0 if self.settings.get("language") == "da" else 1) + lang_sizer.Add(self.lang_choice, 1, wx.EXPAND) + + inner_vbox.Add(lang_sizer, 0, wx.EXPAND | wx.ALL, 10) + + panel.SetSizer(inner_vbox) + inner_vbox.Fit(panel) + vbox.Add(panel, 1, wx.EXPAND | wx.ALL, 0) + + # --- Buttons --- + btn_hbox = wx.BoxSizer(wx.HORIZONTAL) + save_btn = wx.Button(self, label=self.get_txt("settings_save"), size=(150, 35)) + save_btn.SetBackgroundColour(wx.Colour(0, 120, 215)) # SharePoint Blue + save_btn.SetForegroundColour(wx.WHITE) + save_btn.Bind(wx.EVT_BUTTON, self.on_save) + + cancel_btn = wx.Button(self, label=self.get_txt("settings_cancel"), size=(100, 35)) + cancel_btn.Bind(wx.EVT_BUTTON, self.on_cancel) + + btn_hbox.Add(save_btn, 0, wx.RIGHT, 10) + btn_hbox.Add(cancel_btn, 0) + + vbox.Add(btn_hbox, 0, wx.ALIGN_RIGHT | wx.ALL, 15) + + self.SetSizer(vbox) + self.Layout() + self.Centre() + + def on_save(self, event): + self.settings["client_id"] = self.client_id_ctrl.GetValue().strip() + self.settings["tenant_id"] = self.tenant_id_ctrl.GetValue().strip() + self.settings["temp_dir"] = self.temp_dir_picker.GetPath() + self.settings["language"] = "da" if self.lang_choice.GetSelection() == 0 else "en" + + 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) + return + + self.EndModal(wx.ID_OK) + + def on_cancel(self, event): + self.EndModal(wx.ID_CANCEL) + class SharePointApp(wx.Frame): def __init__(self): self.lang = CURRENT_LANG @@ -283,6 +424,10 @@ class SharePointApp(wx.Frame): icon_path = os.path.join(RESOURCE_DIR, "icon.ico") if os.path.exists(icon_path): self.SetIcon(wx.Icon(icon_path, wx.BITMAP_TYPE_ICO)) + + # Start indlæsning (Check for konfiguration) + if not is_configured(settings): + wx.CallAfter(self.on_settings_clicked, None) def get_txt(self, key, **kwargs): text = STRINGS[self.lang].get(key, key) if kwargs: @@ -359,11 +504,10 @@ class SharePointApp(wx.Frame): self.login_btn.Bind(wx.EVT_BUTTON, self.login) nav_hbox.Add(self.login_btn, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 10) - # SPROG VÆLGER - self.lang_choice = wx.Choice(nav_panel, choices=["Dansk", "English"]) - self.lang_choice.SetSelection(0 if self.lang == "da" else 1) - self.lang_choice.Bind(wx.EVT_CHOICE, self.on_language_changed) - nav_hbox.Add(self.lang_choice, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 10) + # INDSTILLINGER KNAP + self.settings_btn = wx.Button(nav_panel, label=self.get_txt("btn_settings"), size=(120, 30)) + self.settings_btn.Bind(wx.EVT_BUTTON, self.on_settings_clicked) + nav_hbox.Add(self.settings_btn, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 10) nav_panel.SetSizer(nav_hbox) vbox.Add(nav_panel, 0, wx.EXPAND | wx.ALL, 5) @@ -724,15 +868,52 @@ class SharePointApp(wx.Frame): self.PopupMenu(menu) menu.Destroy() - def on_language_changed(self, event): - selection = self.lang_choice.GetSelection() - self.lang = "da" if selection == 0 else "en" - - # Gem til settings - settings["language"] = self.lang - save_settings(settings) - - # Opdater UI tekster med det samme + def on_settings_clicked(self, event): + dlg = SettingsDialog(self, settings) + if dlg.ShowModal() == wx.ID_OK: + global CLIENT_ID, TENANT_ID, AUTHORITY, TEMP_DIR, CURRENT_LANG + new_settings = dlg.settings + + # Check if IDs changed (need refresh) + ids_changed = (new_settings["client_id"] != settings["client_id"] or + new_settings["tenant_id"] != settings["tenant_id"]) + + # Save + save_settings(new_settings) + + # Update global variables for current session + settings.update(new_settings) + CLIENT_ID = settings["client_id"] + TENANT_ID = settings["tenant_id"] + AUTHORITY = f"https://login.microsoftonline.com/{TENANT_ID}" + TEMP_DIR = settings["temp_dir"] + + # Apply language + if CURRENT_LANG != new_settings["language"]: + CURRENT_LANG = new_settings["language"] + self.lang = CURRENT_LANG + self.refresh_ui_texts() + + # Update MSAL App if IDs changed + if ids_changed: + self.msal_app = msal.PublicClientApplication(CLIENT_ID, authority=AUTHORITY) + self.access_token = None + self.headers = {} + self.login_btn.Enable() + self.login_btn.SetLabel(self.get_txt("btn_login")) + self.login_btn.SetBackgroundColour(wx.Colour(40, 167, 100)) + self.show_info(self.get_txt("msg_restart_required"), wx.ICON_INFORMATION) + + # Ensure temp dir exists + if not os.path.exists(TEMP_DIR): + os.makedirs(TEMP_DIR) + + self.show_info(self.get_txt("msg_settings_saved")) + + dlg.Destroy() + + def refresh_ui_texts(self): + # Update UI texts for main frame and buttons self.SetTitle(self.get_txt("title")) self.back_btn.SetLabel(self.get_txt("btn_back")) self.home_btn.SetLabel(self.get_txt("btn_home")) @@ -741,19 +922,14 @@ class SharePointApp(wx.Frame): self.upload_folder_btn.SetLabel(self.get_txt("btn_upload_folder")) self.new_folder_btn.SetLabel(self.get_txt("btn_new_folder")) self.refresh_btn.SetLabel(self.get_txt("btn_refresh")) + self.settings_btn.SetLabel(self.get_txt("btn_settings")) if self.access_token: self.login_btn.SetLabel(self.get_txt("btn_logged_in")) else: self.login_btn.SetLabel(self.get_txt("btn_login")) - # Opdater kolonner i ListCtrl - self.list_ctrl.SetColumnWidth(0, 450) # Refresh widths - item = self.list_ctrl.GetColumn(0) - self.list_ctrl.SetColumn(0, wx.ListItem()) # Reset column header logic could be complex in wx, - # but the simplest is to just re-insert columns or set text - - # Re-set headers (Fix: explicitly set image to -1 to avoid icons in headers) + # 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")] for i, text in enumerate(cols): info = self.list_ctrl.GetColumn(i) @@ -762,7 +938,11 @@ class SharePointApp(wx.Frame): self.list_ctrl.SetColumn(i, info) self.set_status(self.get_txt("status_ready")) - self._refresh_current_view() # Gendanner list-item tekster (Mappe/Fil) + self._refresh_current_view() + + def on_language_changed(self, event): + # Deprecated: use on_settings_clicked instead if you want or keep for quick switch + pass # We'll just remove or redirect it later def on_close_window(self, event): if self.active_edits: