From 1aa74ec014ea086aa0479793ad6b391539d4e342 Mon Sep 17 00:00:00 2001 From: duffyduck Date: Fri, 3 Apr 2026 11:04:57 +0200 Subject: [PATCH] Add Outlook folder browser and user settings - Replace Outlook folder ComboBox with TextBox + Browse button that opens a TreeView-based folder browser dialog - Add per-user settings stored in %AppData% (not HKLM) with option to start minimized (System Tray only) - Add Settings button to main window - Settings are per-user, independent of profiles Co-Authored-By: Claude Opus 4.6 (1M context) --- .../Models/UserSettings.cs | 37 ++++++ src/StarfaceOutlookSync/UI/MainForm.cs | 39 +++++- .../UI/OutlookFolderBrowserForm.cs | 125 ++++++++++++++++++ .../UI/ProfileEditorForm.cs | 72 ++++++---- src/StarfaceOutlookSync/UI/SettingsForm.cs | 62 +++++++++ 5 files changed, 306 insertions(+), 29 deletions(-) create mode 100644 src/StarfaceOutlookSync/Models/UserSettings.cs create mode 100644 src/StarfaceOutlookSync/UI/OutlookFolderBrowserForm.cs create mode 100644 src/StarfaceOutlookSync/UI/SettingsForm.cs diff --git a/src/StarfaceOutlookSync/Models/UserSettings.cs b/src/StarfaceOutlookSync/Models/UserSettings.cs new file mode 100644 index 00000000..2ed19bf1 --- /dev/null +++ b/src/StarfaceOutlookSync/Models/UserSettings.cs @@ -0,0 +1,37 @@ +using System; +using System.IO; +using Newtonsoft.Json; + +namespace StarfaceOutlookSync.Models +{ + public class UserSettings + { + public bool StartMinimized { get; set; } = false; + + private static readonly string SettingsFile = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), + "StarfaceOutlookSync", "settings.json"); + + public static UserSettings Load() + { + try + { + if (File.Exists(SettingsFile)) + return JsonConvert.DeserializeObject(File.ReadAllText(SettingsFile)) + ?? new UserSettings(); + } + catch { } + return new UserSettings(); + } + + public void Save() + { + try + { + Directory.CreateDirectory(Path.GetDirectoryName(SettingsFile)); + File.WriteAllText(SettingsFile, JsonConvert.SerializeObject(this, Formatting.Indented)); + } + catch { } + } + } +} diff --git a/src/StarfaceOutlookSync/UI/MainForm.cs b/src/StarfaceOutlookSync/UI/MainForm.cs index 40bb020e..35170ffe 100644 --- a/src/StarfaceOutlookSync/UI/MainForm.cs +++ b/src/StarfaceOutlookSync/UI/MainForm.cs @@ -18,7 +18,7 @@ namespace StarfaceOutlookSync.UI private NotifyIcon _trayIcon; private ContextMenuStrip _trayMenu; private ListView _profileList; - private Button _btnNew, _btnEdit, _btnDelete, _btnSync, _btnInfo; + private Button _btnNew, _btnEdit, _btnDelete, _btnSync, _btnSettings, _btnInfo; private StatusStrip _statusBar; private ToolStripStatusLabel _statusLabel; private Timer _autoSyncTimer; @@ -29,6 +29,30 @@ namespace StarfaceOutlookSync.UI SetupTrayIcon(); SetupAutoSync(); RefreshProfileList(); + + // Minimiert starten falls in Einstellungen aktiviert + var settings = UserSettings.Load(); + if (settings.StartMinimized) + { + WindowState = FormWindowState.Minimized; + ShowInTaskbar = false; + Visible = false; + } + } + + protected override void SetVisibleCore(bool value) + { + // Beim ersten Anzeigen pruefen ob minimiert gestartet werden soll + if (!IsHandleCreated) + { + var settings = UserSettings.Load(); + if (settings.StartMinimized) + { + CreateHandle(); + value = false; + } + } + base.SetVisibleCore(value); } private void InitializeComponent() @@ -76,10 +100,13 @@ namespace StarfaceOutlookSync.UI _btnSync = new Button { Text = "Jetzt synchronisieren", Width = 150, Height = 30 }; _btnSync.Click += async (s, e) => await SyncSelectedProfile(); + _btnSettings = new Button { Text = "Einstellungen", Width = 100, Height = 30 }; + _btnSettings.Click += (s, e) => ShowSettings(); + _btnInfo = new Button { Text = "Info", Width = 50, Height = 30 }; _btnInfo.Click += (s, e) => ShowAbout(); - buttonPanel.Controls.AddRange(new Control[] { _btnNew, _btnEdit, _btnDelete, _btnSync, _btnInfo }); + buttonPanel.Controls.AddRange(new Control[] { _btnNew, _btnEdit, _btnDelete, _btnSync, _btnSettings, _btnInfo }); // Statusbar _statusBar = new StatusStrip(); @@ -283,6 +310,14 @@ namespace StarfaceOutlookSync.UI _statusLabel.Text = text; } + private void ShowSettings() + { + using (var settings = new SettingsForm()) + { + settings.ShowDialog(this); + } + } + private void ShowAbout() { using (var about = new AboutForm()) diff --git a/src/StarfaceOutlookSync/UI/OutlookFolderBrowserForm.cs b/src/StarfaceOutlookSync/UI/OutlookFolderBrowserForm.cs new file mode 100644 index 00000000..885a31a9 --- /dev/null +++ b/src/StarfaceOutlookSync/UI/OutlookFolderBrowserForm.cs @@ -0,0 +1,125 @@ +using System; +using System.Drawing; +using System.Windows.Forms; +using System.Collections.Generic; + +namespace StarfaceOutlookSync.UI +{ + public class OutlookFolderBrowserForm : Form + { + private TreeView _tree; + private Button _btnOk, _btnCancel; + public string SelectedFolderPath { get; private set; } = ""; + public string SelectedFolderName { get; private set; } = ""; + + public OutlookFolderBrowserForm(List folderPaths, string currentSelection) + { + InitializeComponent(); + BuildTree(folderPaths, currentSelection); + } + + private void InitializeComponent() + { + Text = "Outlook-Ordner waehlen"; + Size = new Size(400, 450); + FormBorderStyle = FormBorderStyle.FixedDialog; + MaximizeBox = false; + MinimizeBox = false; + StartPosition = FormStartPosition.CenterParent; + Font = new Font("Segoe UI", 9); + + var lblInfo = new Label + { + Text = "Bitte waehlen Sie den Outlook-Kontaktordner fuer die Synchronisation:", + Left = 12, Top = 12, Width = 360, Height = 36 + }; + + _tree = new TreeView + { + Left = 12, Top = 52, Width = 360, Height = 310, + HideSelection = false, + ShowLines = true, + ShowPlusMinus = true, + ShowRootLines = true + }; + _tree.DoubleClick += (s, e) => { if (_tree.SelectedNode?.Tag != null) SelectAndClose(); }; + + _btnOk = new Button + { + Text = "OK", Left = 210, Top = 372, Width = 75, Height = 28, + DialogResult = DialogResult.None + }; + _btnOk.Click += (s, e) => SelectAndClose(); + + _btnCancel = new Button + { + Text = "Abbrechen", Left = 292, Top = 372, Width = 80, Height = 28, + DialogResult = DialogResult.Cancel + }; + + Controls.AddRange(new Control[] { lblInfo, _tree, _btnOk, _btnCancel }); + AcceptButton = _btnOk; + CancelButton = _btnCancel; + } + + private void BuildTree(List folderPaths, string currentSelection) + { + _tree.Nodes.Clear(); + var nodeMap = new Dictionary(); + + foreach (var path in folderPaths) + { + // Pfad: \\Store\Kontakte\Unterordner + var parts = path.TrimStart('\\').Split('\\'); + string runningPath = ""; + TreeNode parent = null; + + for (int i = 0; i < parts.Length; i++) + { + runningPath += "\\" + parts[i]; + + if (!nodeMap.ContainsKey(runningPath)) + { + var node = new TreeNode(parts[i]); + // Nur Blatt-Knoten oder bekannte Pfade sind waehlbar + if (i == parts.Length - 1) + node.Tag = path; // Vollstaendiger Pfad + + if (parent == null) + _tree.Nodes.Add(node); + else + parent.Nodes.Add(node); + + nodeMap[runningPath] = node; + + // Aktuellen Ordner vorauswaehlen + if (path == currentSelection) + { + _tree.SelectedNode = node; + node.EnsureVisible(); + } + } + + parent = nodeMap[runningPath]; + } + } + + _tree.ExpandAll(); + } + + private void SelectAndClose() + { + if (_tree.SelectedNode?.Tag == null) + { + MessageBox.Show("Bitte einen Kontaktordner waehlen.", "Hinweis", + MessageBoxButtons.OK, MessageBoxIcon.Information); + return; + } + + SelectedFolderPath = _tree.SelectedNode.Tag.ToString(); + SelectedFolderName = _tree.SelectedNode.Text; + DialogResult = DialogResult.OK; + Close(); + } + } +} diff --git a/src/StarfaceOutlookSync/UI/ProfileEditorForm.cs b/src/StarfaceOutlookSync/UI/ProfileEditorForm.cs index cca02f8b..86f54781 100644 --- a/src/StarfaceOutlookSync/UI/ProfileEditorForm.cs +++ b/src/StarfaceOutlookSync/UI/ProfileEditorForm.cs @@ -15,14 +15,18 @@ namespace StarfaceOutlookSync.UI private readonly bool _isNew; // Controls - private TextBox _txtName, _txtHost, _txtPort, _txtLoginId, _txtPassword; + private TextBox _txtName, _txtHost, _txtPort, _txtLoginId, _txtPassword, _txtOutlookFolder; private CheckBox _chkSsl, _chkEnabled; - private ComboBox _cmbAddressBook, _cmbOutlookFolder, _cmbDirection; + private ComboBox _cmbAddressBook, _cmbDirection; + private Button _btnBrowseFolder; private NumericUpDown _numAutoSync; private Button _btnTest, _btnLoadBooks, _btnSave, _btnCancel; private Label _lblTestResult; private List _addressBooks = new List(); + private List _outlookFolderPaths = new List(); + private string _selectedOutlookPath = ""; + private string _selectedOutlookName = ""; public ProfileEditorForm(SyncProfile profile) { @@ -87,8 +91,11 @@ namespace StarfaceOutlookSync.UI panel.Controls.Add(MakeSectionLabel("Outlook-Einstellungen", 12, y)); y += 26; panel.Controls.Add(MakeLabel("Kontakte-Ordner:", 12, y)); y += 22; - _cmbOutlookFolder = new ComboBox { Left = 12, Top = y, Width = 420, DropDownStyle = ComboBoxStyle.DropDownList }; - panel.Controls.Add(_cmbOutlookFolder); y += 32; + _txtOutlookFolder = new TextBox { Left = 12, Top = y, Width = 330, ReadOnly = true, BackColor = SystemColors.Window }; + _btnBrowseFolder = new Button { Text = "Durchsuchen...", Left = 348, Top = y - 1, Width = 84, Height = 24 }; + _btnBrowseFolder.Click += (s, e) => BrowseOutlookFolder(); + panel.Controls.Add(_txtOutlookFolder); + panel.Controls.Add(_btnBrowseFolder); y += 32; panel.Controls.Add(MakeLabel("Sync-Richtung:", 12, y)); y += 22; _cmbDirection = new ComboBox { Left = 12, Top = y, Width = 250, DropDownStyle = ComboBoxStyle.DropDownList }; @@ -132,18 +139,12 @@ namespace StarfaceOutlookSync.UI { using (var outlook = new OutlookContactsService()) { - var folders = outlook.GetContactFolderPaths(); - _cmbOutlookFolder.Items.Clear(); - foreach (var f in folders) - _cmbOutlookFolder.Items.Add(f); - if (folders.Count > 0) - _cmbOutlookFolder.SelectedIndex = 0; + _outlookFolderPaths = outlook.GetContactFolderPaths(); } } catch { - _cmbOutlookFolder.Items.Add("\\\\Kontakte"); - _cmbOutlookFolder.SelectedIndex = 0; + _outlookFolderPaths = new List { "\\\\Kontakte" }; } // Bestehende Werte laden @@ -160,15 +161,10 @@ namespace StarfaceOutlookSync.UI _cmbDirection.SelectedIndex = (int)_existingProfile.SyncDirection; - // Outlook-Ordner auswaehlen - for (int i = 0; i < _cmbOutlookFolder.Items.Count; i++) - { - if (_cmbOutlookFolder.Items[i].ToString() == _existingProfile.OutlookFolderPath) - { - _cmbOutlookFolder.SelectedIndex = i; - break; - } - } + // Outlook-Ordner + _selectedOutlookPath = _existingProfile.OutlookFolderPath; + _selectedOutlookName = _existingProfile.OutlookFolderName; + _txtOutlookFolder.Text = _selectedOutlookPath; // Adressbuch if (_existingProfile.StarfaceAddressBook != null) @@ -178,6 +174,26 @@ namespace StarfaceOutlookSync.UI _cmbAddressBook.SelectedIndex = 0; } } + else if (_outlookFolderPaths.Count > 0) + { + // Standard-Ordner vorauswaehlen + _selectedOutlookPath = _outlookFolderPaths[0]; + _selectedOutlookName = _selectedOutlookPath.Substring(_selectedOutlookPath.LastIndexOf('\\') + 1); + _txtOutlookFolder.Text = _selectedOutlookPath; + } + } + + private void BrowseOutlookFolder() + { + using (var browser = new OutlookFolderBrowserForm(_outlookFolderPaths, _selectedOutlookPath)) + { + if (browser.ShowDialog(this) == DialogResult.OK) + { + _selectedOutlookPath = browser.SelectedFolderPath; + _selectedOutlookName = browser.SelectedFolderName; + _txtOutlookFolder.Text = _selectedOutlookPath; + } + } } private StarfaceConnection GetConnection() @@ -292,10 +308,12 @@ namespace StarfaceOutlookSync.UI return; } - var selectedFolder = _cmbOutlookFolder.SelectedItem?.ToString() ?? ""; - var folderName = selectedFolder; - if (folderName.Contains("\\")) - folderName = folderName.Substring(folderName.LastIndexOf('\\') + 1); + if (string.IsNullOrEmpty(_selectedOutlookPath)) + { + MessageBox.Show("Bitte einen Outlook-Kontaktordner waehlen.", + "Fehler", MessageBoxButtons.OK, MessageBoxIcon.Warning); + return; + } var profile = new SyncProfile { @@ -303,8 +321,8 @@ namespace StarfaceOutlookSync.UI Name = _txtName.Text.Trim(), StarfaceConnection = GetConnection(), StarfaceAddressBook = _addressBooks[_cmbAddressBook.SelectedIndex], - OutlookFolderPath = selectedFolder, - OutlookFolderName = folderName, + OutlookFolderPath = _selectedOutlookPath, + OutlookFolderName = _selectedOutlookName, SyncDirection = (SyncDirection)_cmbDirection.SelectedIndex, Enabled = _chkEnabled.Checked, AutoSyncIntervalMinutes = (int)_numAutoSync.Value, diff --git a/src/StarfaceOutlookSync/UI/SettingsForm.cs b/src/StarfaceOutlookSync/UI/SettingsForm.cs new file mode 100644 index 00000000..29874f0a --- /dev/null +++ b/src/StarfaceOutlookSync/UI/SettingsForm.cs @@ -0,0 +1,62 @@ +using System.Drawing; +using System.Windows.Forms; +using StarfaceOutlookSync.Models; + +namespace StarfaceOutlookSync.UI +{ + public class SettingsForm : Form + { + private CheckBox _chkStartMinimized; + private Button _btnSave, _btnCancel; + private readonly UserSettings _settings; + + public SettingsForm() + { + _settings = UserSettings.Load(); + InitializeComponent(); + } + + private void InitializeComponent() + { + Text = "Einstellungen"; + Size = new Size(350, 180); + FormBorderStyle = FormBorderStyle.FixedDialog; + MaximizeBox = false; + MinimizeBox = false; + StartPosition = FormStartPosition.CenterParent; + Font = new Font("Segoe UI", 9); + + _chkStartMinimized = new CheckBox + { + Text = "Minimiert starten (nur im System Tray)", + Left = 20, Top = 24, AutoSize = true, + Checked = _settings.StartMinimized + }; + + _btnSave = new Button + { + Text = "Speichern", Left = 80, Top = 100, Width = 85, Height = 28, + DialogResult = DialogResult.None + }; + _btnSave.Click += (s, e) => Save(); + + _btnCancel = new Button + { + Text = "Abbrechen", Left = 174, Top = 100, Width = 85, Height = 28, + DialogResult = DialogResult.Cancel + }; + + Controls.AddRange(new Control[] { _chkStartMinimized, _btnSave, _btnCancel }); + AcceptButton = _btnSave; + CancelButton = _btnCancel; + } + + private void Save() + { + _settings.StartMinimized = _chkStartMinimized.Checked; + _settings.Save(); + DialogResult = DialogResult.OK; + Close(); + } + } +}