mirror of
https://github.com/Kopano-dev/kopano-ol-extension.git
synced 2023-10-10 11:37:40 +00:00
1075 lines
42 KiB
C#
1075 lines
42 KiB
C#
/// Copyright 2018 Kopano b.v.
|
|
///
|
|
/// This program is free software: you can redistribute it and/or modify
|
|
/// it under the terms of the GNU Affero General Public License, version 3,
|
|
/// as published by the Free Software Foundation.
|
|
///
|
|
/// This program is distributed in the hope that it will be useful,
|
|
/// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
|
|
/// GNU Affero General Public License for more details.
|
|
///
|
|
/// You should have received a copy of the GNU Affero General Public License
|
|
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
|
|
///
|
|
/// Consult LICENSE file for details
|
|
|
|
using Acacia.Controls;
|
|
using Acacia.Features.GAB;
|
|
using Acacia.Features.SendAs;
|
|
using Acacia.Stubs;
|
|
using Acacia.UI;
|
|
using Acacia.UI.Outlook;
|
|
using Acacia.ZPush;
|
|
using Acacia.ZPush.API.SharedFolders;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Data;
|
|
using System.Drawing;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.Windows.Forms;
|
|
|
|
namespace Acacia.Features.SharedFolders
|
|
{
|
|
public partial class SharedFoldersDialog : KDialogNew
|
|
{
|
|
/// <summary>
|
|
/// Check manager that makes the store check box independent of the folder checkboxes, which are
|
|
/// still applied recursively
|
|
/// </summary>
|
|
private class ShareCheckManager : KCheckManager.RecursiveThreeState
|
|
{
|
|
public override KCheckStyle CheckStyle { get { return KCheckStyle.Custom; } }
|
|
|
|
protected override void SetParentCheckState(KTreeNode parent, CheckState childCheckState)
|
|
{
|
|
// The store node state is independent of the rest
|
|
if (parent == null || parent is StoreTreeNode)
|
|
return;
|
|
|
|
base.SetParentCheckState(parent, childCheckState);
|
|
}
|
|
|
|
protected override void SetNodeCheckState(KTreeNode node, CheckState checkState)
|
|
{
|
|
// The store node state is independent of the rest
|
|
if (node is StoreTreeNode)
|
|
node.CheckState = checkState;
|
|
else
|
|
base.SetNodeCheckState(node, checkState);
|
|
}
|
|
|
|
protected override CheckState NextCheckState(KTreeNode node)
|
|
{
|
|
if (node is StoreTreeNode)
|
|
{
|
|
// The store node has a two-state checkbox
|
|
return (node.CheckState == CheckState.Checked) ? CheckState.Unchecked : CheckState.Checked;
|
|
}
|
|
else
|
|
{
|
|
return base.NextCheckState(node);
|
|
}
|
|
}
|
|
|
|
public override void SetCheck(KTreeNode node, CheckState state)
|
|
{
|
|
if (node is StoreTreeNode)
|
|
{
|
|
node.CheckStateDirect = state;
|
|
}
|
|
else
|
|
{
|
|
base.SetCheck(node, state);
|
|
}
|
|
}
|
|
}
|
|
|
|
private readonly FeatureSharedFolders _feature;
|
|
private readonly FeatureSendAs _featureSendAs;
|
|
private readonly ZPushAccount _account;
|
|
private readonly SharedFoldersManager _folders;
|
|
private readonly SyncId _initialSyncId;
|
|
private ZPushAccount _initialAccount;
|
|
private SharedFolder _initialFolder;
|
|
|
|
public bool SuppressInitialSendAsWarning
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
public SharedFoldersDialog(FeatureSharedFolders feature, ZPushAccount account, SyncId initial = null)
|
|
{
|
|
// If this is a shared store, open the account it's a share for, with the request account as the initial
|
|
if (account.ShareFor != null)
|
|
{
|
|
_initialAccount = account;
|
|
account = account.ShareForAccount;
|
|
}
|
|
this._account = account;
|
|
this._feature = feature;
|
|
this._featureSendAs = ThisAddIn.Instance.GetFeature<FeatureSendAs>();
|
|
this._folders = feature.Manage(account);
|
|
this._initialSyncId = initial;
|
|
|
|
InitializeComponent();
|
|
|
|
// TODO: make a specialised class out of this
|
|
this.kTreeFolders.Images = new OutlookImageList(
|
|
"NewFolder", // Other
|
|
"JunkEmailMarkAsNotJunk", // Inbox
|
|
"GoDrafts", // Drafts
|
|
"RecycleBin", // WasteBasket
|
|
"ReceiveMenu", // SentMail
|
|
"NewFolder", // Outbox
|
|
"ShowTaskPage", // Task
|
|
"ShowAppointmentPage", // Appointment
|
|
"ShowContactPage", // Contact
|
|
"NewNote", // Note
|
|
"ShowJournalPage", // Journal
|
|
"LastModifiedBy" // Store
|
|
|
|
).Images;
|
|
|
|
// Set the check manager
|
|
kTreeFolders.CheckManager = new ShareCheckManager();
|
|
|
|
// Add the email address to the title
|
|
Text = string.Format(Text, account.Account.SmtpAddress);
|
|
|
|
// Set up options
|
|
ShowOptions(new KTreeNode[0]);
|
|
|
|
// Set up user selector
|
|
gabLookup.GAB = FeatureGAB.FindGABForAccount(account);
|
|
}
|
|
|
|
#region Load and store
|
|
|
|
private void AddSharedFolderDialog_Shown(object sender, EventArgs args)
|
|
{
|
|
BusyText = Properties.Resources.SharedFolders_Fetching_Label;
|
|
KUITask
|
|
.New((ctx) =>
|
|
{
|
|
// TODO: bind cancellation token to Cancel button
|
|
|
|
// Fetch current shares
|
|
ICollection<SharedFolder> folders = _folders.GetCurrentShares(ctx.CancellationToken);
|
|
|
|
// Find the initial folder if required
|
|
if (_initialSyncId != null)
|
|
_initialFolder = folders.FirstOrDefault(f => f.SyncId == _initialSyncId);
|
|
|
|
// Group by store and folder id
|
|
return folders.GroupBy(f => f.Store)
|
|
.ToDictionary(group => group.Key,
|
|
group => group.ToDictionary(folder => folder.BackendId));
|
|
})
|
|
.OnSuccess(InitialiseTree, true)
|
|
.OnError((e) =>
|
|
{
|
|
UI.ErrorUtil.HandleErrorNew(typeof(FeatureSharedFolders), "Exception fetching shared folders for account {0}", e,
|
|
Properties.Resources.SharedFolders_Fetching_Title,
|
|
Properties.Resources.SharedFolders_Fetching_Failure,
|
|
_account.DisplayName);
|
|
DialogResult = DialogResult.Cancel;
|
|
})
|
|
.Start(this)
|
|
;
|
|
}
|
|
|
|
private void InitialiseTree(KUITaskContext context, Dictionary<GABUser, Dictionary<BackendId, SharedFolder>> shares)
|
|
{
|
|
kTreeFolders.BeginUpdate();
|
|
try
|
|
{
|
|
// Add public folders
|
|
Dictionary<BackendId, SharedFolder> publicShares;
|
|
shares.TryGetValue(GABUser.USER_PUBLIC, out publicShares);
|
|
AddUserFolders(GABUser.USER_PUBLIC, null, publicShares, false);
|
|
|
|
// Add shared stores
|
|
foreach (ZPushAccount shared in _account.SharedAccounts)
|
|
{
|
|
AddUserFolders(new GABUser(shared.ShareUserName), shared, null, false);
|
|
}
|
|
|
|
// Add any users for which we have shared folders
|
|
foreach (KeyValuePair<GABUser, Dictionary<BackendId, SharedFolder>> entry in shares.OrderBy(x => x.Key.DisplayName))
|
|
if (GABUser.USER_PUBLIC != entry.Key)
|
|
AddUserFolders(entry.Key, null, entry.Value, false);
|
|
}
|
|
finally
|
|
{
|
|
kTreeFolders.EndUpdate();
|
|
}
|
|
|
|
// Try to select initial node
|
|
if (_initialFolder != null)
|
|
{
|
|
StoreTreeNode node;
|
|
if (_userFolders.TryGetValue(_initialFolder.Store, out node))
|
|
{
|
|
// Keep indicating busy until it's done
|
|
context.AddBusy(1);
|
|
node.NodesLoaded += (_) =>
|
|
{
|
|
KTreeNode folderNode = node.FindNode(_initialFolder);
|
|
if (folderNode != null)
|
|
FocusNode(folderNode, true);
|
|
context.AddBusy(-1);
|
|
};
|
|
FocusNode(node, true);
|
|
}
|
|
SetInitialFocus(kTreeFolders);
|
|
}
|
|
else if (_initialAccount != null)
|
|
{
|
|
StoreTreeNode node;
|
|
if (_userFolders.TryGetValue(new GABUser(_initialAccount.ShareUserName), out node))
|
|
{
|
|
FocusNode(node, true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SetInitialFocus(gabLookup);
|
|
}
|
|
}
|
|
|
|
private void SetInitialFocus(Control control)
|
|
{
|
|
// If busy, setting the focus doesn't work, as the control is enabled. Wait until done.
|
|
BusyHider.OnDoneBusy(() =>
|
|
{
|
|
control.Focus();
|
|
});
|
|
}
|
|
|
|
private class ApplyState
|
|
{
|
|
public int folders;
|
|
public readonly List<StoreTreeNode> stores = new List<StoreTreeNode>();
|
|
}
|
|
|
|
private void dialogButtons_Apply(object sender, EventArgs e)
|
|
{
|
|
int folderCount = 0;
|
|
|
|
// Check if all fields are properly set
|
|
foreach (StoreTreeNode storeNode in _userFolders.Values)
|
|
{
|
|
// Check totals
|
|
folderCount += storeNode.CurrentShares.Count();
|
|
|
|
// Check modified folders
|
|
if (storeNode.IsDirty)
|
|
{
|
|
foreach(SharedFolder folder in storeNode.CurrentShares)
|
|
{
|
|
// Check if the send-as address has been resolved (or entered) correctly
|
|
if (folder.FlagSendAsOwner && string.IsNullOrWhiteSpace(folder.SendAsAddress))
|
|
{
|
|
// Find the tree node
|
|
KTreeNode folderNode = storeNode.FindNode(folder);
|
|
if (folderNode != null)
|
|
{
|
|
// See if we can obtain it from a parent
|
|
TryInitSendAsAddressParent(folderNode as FolderTreeNode);
|
|
if (!string.IsNullOrWhiteSpace(folder.SendAsAddress))
|
|
continue;
|
|
|
|
// If the node is already selected, explicitly warn about the send-as address
|
|
// Otherwise, selecting it will pop up the warning
|
|
if (folderNode.IsSelected)
|
|
TryInitSendAsAddress();
|
|
else
|
|
FocusNode(folderNode, false);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check total number of folders
|
|
if (folderCount >= _feature.MaxFolderCount)
|
|
{
|
|
MessageBox.Show(ThisAddIn.Instance.Window,
|
|
Properties.Resources.SharedFolders_TooManyFolders_Body,
|
|
Properties.Resources.SharedFolders_TooManyFolders_Title,
|
|
MessageBoxButtons.OK,
|
|
MessageBoxIcon.Error
|
|
);
|
|
// And don't apply anything
|
|
return;
|
|
}
|
|
|
|
BusyText = Properties.Resources.SharedFolders_Applying_Label;
|
|
KUITask.New((ctx) =>
|
|
{
|
|
// We reuse the same busy indicationg for all calls. A count is kept to ensure it's removed.
|
|
ApplyState state = new ApplyState();
|
|
|
|
foreach (StoreTreeNode storeNode in _userFolders.Values)
|
|
{
|
|
// Check modified folders
|
|
if (storeNode.IsDirty)
|
|
{
|
|
ctx.AddBusy(1);
|
|
++state.folders;
|
|
|
|
// Check removed shares
|
|
_folders.RemoveSharesForStore(storeNode.User, storeNode.RemovedShares);
|
|
|
|
// Set shares
|
|
_folders.SetSharesForStore(storeNode.User, storeNode.CurrentShares, ctx.CancellationToken);
|
|
}
|
|
|
|
// And modified stores
|
|
if (storeNode.IsWholeStoreDirty)
|
|
{
|
|
state.stores.Add(storeNode);
|
|
}
|
|
}
|
|
|
|
return state;
|
|
})
|
|
.OnSuccess((ctx, state) =>
|
|
{
|
|
// Update UI state
|
|
foreach (StoreTreeNode storeNode in _userFolders.Values)
|
|
if (storeNode.IsDirty)
|
|
storeNode.ChangesApplied();
|
|
|
|
ctx.AddBusy(-state.folders);
|
|
|
|
List<ZPushAccount> syncAdditional = new List<ZPushAccount>();
|
|
|
|
// Handle stores
|
|
if (state.stores.Count > 0)
|
|
{
|
|
List<StoreTreeNode> add = new List<StoreTreeNode>();
|
|
|
|
// Remove any unshared store
|
|
foreach (StoreTreeNode store in state.stores)
|
|
{
|
|
if (store.WantShare)
|
|
{
|
|
// Check if it must be added
|
|
if (!store.IsShared)
|
|
add.Add(store);
|
|
|
|
// Update reminders for existing stores
|
|
if (store.ShowReminders != store.ShowRemindersInitial)
|
|
{
|
|
ZPushAccount storeAccount = store.WholeStoreAccount;
|
|
if (storeAccount != null)
|
|
{
|
|
storeAccount.ShowReminders = store.ShowReminders;
|
|
syncAdditional.Add(storeAccount);
|
|
// Update UI state
|
|
store.ShowRemindersInitial = store.ShowReminders;
|
|
WholeStoreShareChanged(store);
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
// Remove it
|
|
_feature.RemoveSharedStore(_account, store.User);
|
|
store.IsShared = false;
|
|
WholeStoreShareChanged(store);
|
|
}
|
|
|
|
}
|
|
|
|
// Check for any new stores
|
|
if (add.Count > 0)
|
|
{
|
|
bool restart = MessageBox.Show(ThisAddIn.Instance.Window,
|
|
Properties.Resources.SharedFolders_WholeStoreRestart_Body,
|
|
Properties.Resources.SharedFolders_WholeStoreRestart_Title,
|
|
MessageBoxButtons.OKCancel,
|
|
MessageBoxIcon.Information
|
|
) == DialogResult.OK;
|
|
|
|
// Reset state. Also do this when restarting, to avoid warning message about unsaved changes
|
|
foreach (StoreTreeNode node in state.stores)
|
|
node.WantShare = node.IsShared;
|
|
|
|
if (!restart)
|
|
return;
|
|
|
|
// Restart
|
|
IRestarter restarter = ThisAddIn.Instance.Restarter();
|
|
restarter.CloseWindows = true;
|
|
foreach (StoreTreeNode node in state.stores)
|
|
restarter.OpenShare(_account, node.User, node.ShowReminders);
|
|
restarter.Restart();
|
|
}
|
|
|
|
// Update UI state
|
|
foreach (StoreTreeNode storeNode in _userFolders.Values)
|
|
storeNode.ChangesApplied();
|
|
CheckDirty();
|
|
}
|
|
|
|
// Sync accounts
|
|
foreach (ZPushAccount account in syncAdditional)
|
|
_feature.Sync(account);
|
|
|
|
if (state.folders != 0)
|
|
{
|
|
// Sync account
|
|
_feature.Sync(_account);
|
|
|
|
// Show success
|
|
ShowCompletion(Properties.Resources.SharedFolders_Applying_Success);
|
|
}
|
|
|
|
}, true)
|
|
.OnError((x) =>
|
|
{
|
|
ErrorUtil.HandleErrorNew(typeof(FeatureSharedFolders), "Exception applying shared folders for account {0}", x,
|
|
Properties.Resources.SharedFolders_Applying_Title,
|
|
Properties.Resources.SharedFolders_Applying_Failure,
|
|
_account.DisplayName);
|
|
})
|
|
.Start(this);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Event handlers
|
|
|
|
private void buttonOpenUser_Click(object sender, EventArgs e)
|
|
{
|
|
AddUserFolders(gabLookup.SelectedUser, null, null, true);
|
|
}
|
|
|
|
private void gabLookup_SelectedUserChanged(object source, GABLookupControl.SelectedUserEventArgs e)
|
|
{
|
|
buttonOpenUser.Enabled = (e.SelectedUser != null);
|
|
|
|
if (e.IsChosen)
|
|
{
|
|
AddUserFolders(e.SelectedUser, null, null, true);
|
|
}
|
|
}
|
|
|
|
private void SharedFoldersDialog_DirtyFormClosing(object sender, FormClosingEventArgs e)
|
|
{
|
|
// Require confirmation before closing a dirty form
|
|
e.Cancel = MessageBox.Show(Properties.Resources.SharedFolders_Unsaved_Changes,
|
|
Text,
|
|
MessageBoxButtons.YesNo,
|
|
MessageBoxIcon.Question
|
|
) != DialogResult.Yes;
|
|
}
|
|
|
|
#endregion
|
|
|
|
private readonly Dictionary<GABUser, StoreTreeNode> _userFolders = new Dictionary<GABUser, StoreTreeNode>();
|
|
|
|
private void AddUserFolders(GABUser user, ZPushAccount wholeStore, Dictionary<BackendId, SharedFolder> currentShares, bool select)
|
|
{
|
|
if (user == null)
|
|
return;
|
|
|
|
// If the user is already fetched, reuse the node
|
|
StoreTreeNode node;
|
|
if (!_userFolders.TryGetValue(user, out node))
|
|
{
|
|
if (!user.HasFullName)
|
|
{
|
|
// Try to fill in the full name
|
|
user = gabLookup.LookupExact(user.UserName);
|
|
}
|
|
|
|
string sendAsAddress = _featureSendAs?.FindSendAsAddress(_account, user);
|
|
|
|
// Add the node
|
|
node = new StoreTreeNode(_folders, gabLookup.GAB,
|
|
user, sendAsAddress,
|
|
user.DisplayName, currentShares ?? new Dictionary<BackendId, SharedFolder>(),
|
|
wholeStore != null,
|
|
wholeStore?.ShowReminders == true);
|
|
node.DirtyChanged += UserSharesChanged;
|
|
node.CheckStateChanged += WholeStoreShareChanged;
|
|
_userFolders.Add(user, node);
|
|
kTreeFolders.RootNodes.Add(node);
|
|
}
|
|
|
|
if (select)
|
|
{
|
|
FocusNode(node, !_folders.SupportsWholeStore);
|
|
}
|
|
}
|
|
|
|
private void FocusNode(KTreeNode node, bool expand)
|
|
{
|
|
// Scroll it to the top of the window
|
|
kTreeFolders.SelectNode(node, KTree.ScrollMode.Top);
|
|
|
|
// Start loading folders if requested
|
|
if (expand)
|
|
node.IsExpanded = true;
|
|
|
|
// Clear any selected user
|
|
gabLookup.SelectedUser = null;
|
|
|
|
// And focus the tree
|
|
kTreeFolders.Focus();
|
|
}
|
|
|
|
private readonly Dictionary<GABUser, bool> _dirtyWholeStores = new Dictionary<GABUser, bool>();
|
|
private readonly Dictionary<GABUser, bool> _dirtyUsers = new Dictionary<GABUser, bool>();
|
|
|
|
private void UserSharesChanged(StoreTreeNode node)
|
|
{
|
|
_dirtyUsers[node.User] = node.IsDirty;
|
|
CheckDirty();
|
|
}
|
|
|
|
private void WholeStoreShareChanged(KTreeNode node)
|
|
{
|
|
// TODO: check duplicate email address
|
|
StoreTreeNode storeNode = (StoreTreeNode)node;
|
|
_dirtyWholeStores[storeNode.User] = storeNode.IsWholeStoreDirty;
|
|
CheckDirty();
|
|
}
|
|
|
|
private void CheckDirty()
|
|
{
|
|
dialogButtons.IsDirty = _dirtyUsers.Values.Any((x) => x) || _dirtyWholeStores.Values.Any((x) => x);
|
|
}
|
|
|
|
#region Advanced options
|
|
|
|
private string OptionName
|
|
{
|
|
get { return textName.Visible ? textName.Text : null; }
|
|
set
|
|
{
|
|
_labelName.Visible = textName.Visible = value != null;
|
|
textName.Text = value ?? "";
|
|
}
|
|
}
|
|
private bool? OptionTrackName
|
|
{
|
|
get { return textName.Visible ? !_labelName.Enabled : (bool?)null; }
|
|
set
|
|
{
|
|
if (value != null)
|
|
{
|
|
_labelName.Enabled = !value.Value;
|
|
}
|
|
}
|
|
}
|
|
private FolderTreeNode _optionNameNode;
|
|
|
|
private CheckState? OptionSendAs
|
|
{
|
|
get
|
|
{
|
|
if (checkSendAs.Visible)
|
|
return checkSendAs.CheckState;
|
|
return null;
|
|
}
|
|
|
|
set
|
|
{
|
|
_labelSendAs.Visible = checkSendAs.Visible = value != null;
|
|
_labelSendAsAddress.Visible = textSendAsAddress.Visible = _labelSendAs.Visible;
|
|
if (value != null)
|
|
checkSendAs.CheckState = value.Value;
|
|
}
|
|
}
|
|
private readonly List<FolderTreeNode> _optionSendAsNodes = new List<FolderTreeNode>();
|
|
private readonly List<bool> _optionSendAsInitial = new List<bool>();
|
|
|
|
private CheckState? OptionReminders
|
|
{
|
|
get
|
|
{
|
|
if (checkReminders.Visible)
|
|
return checkReminders.CheckState;
|
|
return null;
|
|
}
|
|
|
|
set
|
|
{
|
|
_labelReminders.Visible = checkReminders.Visible = value != null;
|
|
if (value != null)
|
|
checkReminders.CheckState = value.Value;
|
|
}
|
|
}
|
|
private readonly List<FolderTreeNode> _optionRemindersNodes = new List<FolderTreeNode>();
|
|
private readonly List<bool> _optionRemindersInitial = new List<bool>();
|
|
|
|
private Permission? _optionPermissions;
|
|
private Permission? OptionPermissions
|
|
{
|
|
get { return _optionPermissions; }
|
|
set
|
|
{
|
|
_optionPermissions = value;
|
|
_labelPermissions.Visible = labelPermissionsValue.Visible = value != null;
|
|
|
|
if (value == null)
|
|
labelPermissionsValue.Text = "";
|
|
else
|
|
{
|
|
// Look up permission string
|
|
switch (value)
|
|
{
|
|
case Permission.None:
|
|
labelPermissionsValue.Text = Properties.Resources.SharedFolders_Permission_None;
|
|
break;
|
|
case Permission.Read:
|
|
labelPermissionsValue.Text = Properties.Resources.SharedFolders_Permission_Read;
|
|
break;
|
|
case Permission.Write:
|
|
labelPermissionsValue.Text = Properties.Resources.SharedFolders_Permission_Write;
|
|
break;
|
|
case Permission.ReadWrite:
|
|
labelPermissionsValue.Text = Properties.Resources.SharedFolders_Permission_Read + " / " + Properties.Resources.SharedFolders_Permission_Write;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
private readonly List<FolderTreeNode> _optionPermissionNodes = new List<FolderTreeNode>();
|
|
|
|
private CheckState? OptionWholeStore
|
|
{
|
|
get
|
|
{
|
|
if (checkWholeStore.Visible)
|
|
return checkWholeStore.CheckState;
|
|
return null;
|
|
}
|
|
|
|
set
|
|
{
|
|
_labelWholeStore.Visible = checkWholeStore.Visible = value != null;
|
|
if (value != null)
|
|
checkWholeStore.CheckState = value.Value;
|
|
}
|
|
}
|
|
private readonly List<StoreTreeNode> _optionWholeStoreNodes = new List<StoreTreeNode>();
|
|
private readonly List<bool> _optionWholeStoreNodesInitial = new List<bool>();
|
|
private CheckState? OptionWholeStoreReminders
|
|
{
|
|
get
|
|
{
|
|
if (checkWholeStoreReminders.Visible)
|
|
return checkWholeStoreReminders.CheckState;
|
|
return null;
|
|
}
|
|
|
|
set
|
|
{
|
|
_labelWholeStoreReminders.Visible = checkWholeStoreReminders.Visible = value != null;
|
|
if (value != null)
|
|
checkWholeStoreReminders.CheckState = value.Value;
|
|
}
|
|
}
|
|
|
|
private void ShowOptions(KTreeNode[] nodes)
|
|
{
|
|
try
|
|
{
|
|
_layoutOptions.SuspendLayout();
|
|
|
|
_optionNameNode = null;
|
|
_optionSendAsNodes.Clear();
|
|
_optionSendAsInitial.Clear();
|
|
_optionRemindersNodes.Clear();
|
|
_optionRemindersInitial.Clear();
|
|
_optionPermissionNodes.Clear();
|
|
_optionWholeStoreNodes.Clear();
|
|
_optionWholeStoreNodesInitial.Clear();
|
|
OptionName = null;
|
|
OptionTrackName = null;
|
|
OptionSendAs = null;
|
|
OptionReminders = null;
|
|
OptionPermissions = null;
|
|
OptionWholeStore = null;
|
|
OptionWholeStoreReminders = null;
|
|
bool readOnly = false;
|
|
bool haveStoreNodes = false;
|
|
bool haveFolderNodes = false;
|
|
|
|
foreach (KTreeNode node in nodes)
|
|
{
|
|
// Ignore the root nodes
|
|
if (node is StoreTreeNode)
|
|
{
|
|
if (!_folders.SupportsWholeStore || !node.HasCheckBox)
|
|
continue;
|
|
|
|
StoreTreeNode storeNode = (StoreTreeNode)node;
|
|
haveStoreNodes = true;
|
|
_optionWholeStoreNodes.Add(storeNode);
|
|
_optionWholeStoreNodesInitial.Add(storeNode.IsShared);
|
|
}
|
|
else
|
|
{
|
|
FolderTreeNode folderNode = (FolderTreeNode)node;
|
|
// Can only set options for shared folders
|
|
if (!folderNode.IsShared)
|
|
continue;
|
|
|
|
haveFolderNodes = true;
|
|
|
|
// Set all controls to read-only if any of the nodes is read-only
|
|
if (folderNode.IsReadOnly)
|
|
readOnly = true;
|
|
|
|
SharedFolder share = folderNode.SharedFolder;
|
|
AvailableFolder folder = folderNode.AvailableFolder;
|
|
|
|
// Assume we will edit the name for this node; cleared below if there are multiple
|
|
_optionNameNode = folderNode;
|
|
|
|
if (folder.Type.IsMail())
|
|
{
|
|
// Show send as if there are any mail folders
|
|
_optionSendAsNodes.Add(folderNode);
|
|
_optionSendAsInitial.Add(folderNode.SharedFolder.FlagSendAsOwner);
|
|
}
|
|
else if (folder.Type.IsAppointment())
|
|
{
|
|
// Show reminders for appointment folders
|
|
_optionRemindersNodes.Add(folderNode);
|
|
_optionRemindersInitial.Add(folderNode.SharedFolder.FlagCalendarReminders);
|
|
}
|
|
|
|
// Show permissions for all shared nodes
|
|
_optionPermissionNodes.Add(folderNode);
|
|
}
|
|
}
|
|
|
|
// Now check consistency of the options
|
|
|
|
if (haveFolderNodes && haveStoreNodes)
|
|
{
|
|
// Mixed nodes, no options
|
|
return;
|
|
}
|
|
|
|
if (haveStoreNodes)
|
|
{
|
|
if (_optionWholeStoreNodes.Count > 0)
|
|
{
|
|
bool isShared = _optionWholeStoreNodes.First().WantShare;
|
|
bool wasShared = _optionWholeStoreNodes.First().IsShared;
|
|
if (_optionWholeStoreNodes.All(x => x.WantShare == isShared))
|
|
{
|
|
OptionWholeStore = isShared ? CheckState.Checked : CheckState.Unchecked;
|
|
|
|
checkWholeStore.ThreeState = false;
|
|
}
|
|
else
|
|
{
|
|
OptionWholeStore = CheckState.Indeterminate;
|
|
checkWholeStore.ThreeState = true;
|
|
}
|
|
|
|
_labelRestartRequired.Visible = isShared && !wasShared;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Only show the name if there is a single node.
|
|
// We do that here so there doesn't have to be duplication if testing if it's sharedd,
|
|
// ect
|
|
if (_optionNameNode != null && nodes.Length == 1)
|
|
{
|
|
OptionName = _optionNameNode.SharedFolder.Name;
|
|
OptionTrackName = _optionNameNode.SharedFolder.FlagUpdateShareName;
|
|
}
|
|
else
|
|
{
|
|
_optionNameNode = null;
|
|
}
|
|
|
|
// Permissions shown if all are the same
|
|
if (_optionPermissionNodes.Count > 0)
|
|
{
|
|
Permission? permissions = _optionPermissionNodes.First().SharedFolder.Permissions;
|
|
if (_optionPermissionNodes.All(x => x.SharedFolder.Permissions == permissions))
|
|
OptionPermissions = permissions;
|
|
}
|
|
|
|
// Send as shown if any node supports it
|
|
if (_optionSendAsNodes.Count > 0)
|
|
{
|
|
bool sendAs = _optionSendAsNodes.First().SharedFolder.FlagSendAsOwner;
|
|
if (_optionSendAsNodes.All(x => x.SharedFolder.FlagSendAsOwner == sendAs))
|
|
{
|
|
OptionSendAs = sendAs ? CheckState.Checked : CheckState.Unchecked;
|
|
checkSendAs.ThreeState = false;
|
|
}
|
|
else
|
|
{
|
|
OptionSendAs = CheckState.Indeterminate;
|
|
checkSendAs.ThreeState = true;
|
|
}
|
|
|
|
textSendAsAddress.Text = TryInitSendAsAddress();
|
|
EnableSendAsAddress();
|
|
}
|
|
// Reminders shown if any node supports it
|
|
if (_optionRemindersNodes.Count > 0)
|
|
{
|
|
bool reminders = _optionRemindersNodes.First().SharedFolder.FlagCalendarReminders;
|
|
if (_optionRemindersNodes.All(x => x.SharedFolder.FlagCalendarReminders == reminders))
|
|
{
|
|
OptionReminders = reminders ? CheckState.Checked : CheckState.Unchecked;
|
|
checkReminders.ThreeState = false;
|
|
}
|
|
else
|
|
{
|
|
OptionReminders = CheckState.Indeterminate;
|
|
checkReminders.ThreeState = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Apply read-only state
|
|
_layoutOptions.Enabled = !readOnly;
|
|
}
|
|
finally
|
|
{
|
|
_layoutOptions.ResumeLayout();
|
|
}
|
|
}
|
|
|
|
private void kTreeFolders_CheckStateChanged(object sender, KTree.CheckStateChangedEventArgs e)
|
|
{
|
|
// If the node is selected, may have to change option display
|
|
if (e.Node.IsSelected)
|
|
{
|
|
ShowOptions(kTreeFolders.SelectedNodes.ToArray());
|
|
}
|
|
}
|
|
|
|
private void kTreeFolders_SelectionChanged(object sender, KTree.SelectionChangedEventArgs e)
|
|
{
|
|
ShowOptions(e.SelectedNodes);
|
|
}
|
|
|
|
private void textName_TextChanged(object sender, EventArgs e)
|
|
{
|
|
if (_optionNameNode != null)
|
|
{
|
|
_optionNameNode.SharedFolder = _optionNameNode.SharedFolder.WithName(textName.Text);
|
|
|
|
// If the share name matches the folder name, track update
|
|
bool track = _optionNameNode.SharedFolder.Name == _optionNameNode.DefaultName;
|
|
_optionNameNode.SharedFolder = _optionNameNode.SharedFolder.WithFlagTrackShareName(track);
|
|
}
|
|
}
|
|
|
|
private void checkWholeStore_CheckedChanged(object sender, EventArgs e)
|
|
{
|
|
CheckState? reminders = null;
|
|
for (int i = 0; i < _optionWholeStoreNodes.Count; ++i)
|
|
{
|
|
StoreTreeNode node = _optionWholeStoreNodes[i];
|
|
bool wholeStore = false;
|
|
switch (checkWholeStore.CheckState)
|
|
{
|
|
case CheckState.Checked: wholeStore = true; break;
|
|
case CheckState.Indeterminate: wholeStore = _optionWholeStoreNodesInitial[i]; break;
|
|
case CheckState.Unchecked: wholeStore = false; break;
|
|
}
|
|
|
|
if (wholeStore)
|
|
{
|
|
CheckState remindersCheck = node.ShowReminders? CheckState.Checked: CheckState.Unchecked;
|
|
if (reminders == null)
|
|
reminders = remindersCheck;
|
|
else if (reminders.Value != remindersCheck)
|
|
reminders = CheckState.Indeterminate;
|
|
}
|
|
node.WantShare = wholeStore;
|
|
}
|
|
|
|
OptionWholeStoreReminders = reminders;
|
|
}
|
|
private void checkWholeStoreReminders_CheckedChanged(object sender, EventArgs e)
|
|
{
|
|
for (int i = 0; i < _optionWholeStoreNodes.Count; ++i)
|
|
{
|
|
StoreTreeNode node = _optionWholeStoreNodes[i];
|
|
switch (checkWholeStoreReminders.CheckState)
|
|
{
|
|
case CheckState.Checked: node.ShowReminders = true; break;
|
|
case CheckState.Indeterminate: node.ShowReminders = node.ShowRemindersInitial; break;
|
|
case CheckState.Unchecked: node.ShowReminders = false; break;
|
|
}
|
|
WholeStoreShareChanged(node);
|
|
}
|
|
}
|
|
|
|
private void checkSendAs_CheckedChanged(object sender, EventArgs e)
|
|
{
|
|
// Hide the address unless it makes sense
|
|
EnableSendAsAddress();
|
|
|
|
for (int i = 0; i < _optionSendAsNodes.Count; ++i)
|
|
{
|
|
FolderTreeNode node = _optionSendAsNodes[i];
|
|
bool sendAs = false;
|
|
switch(checkSendAs.CheckState)
|
|
{
|
|
case CheckState.Checked: sendAs = true; break;
|
|
case CheckState.Indeterminate: sendAs = _optionSendAsInitial[i]; break;
|
|
case CheckState.Unchecked: sendAs = false; break;
|
|
}
|
|
|
|
if (node.SharedFolder.FlagSendAsOwner != sendAs)
|
|
{
|
|
node.SharedFolder = node.SharedFolder.WithFlagSendAsOwner(sendAs);
|
|
if (sendAs)
|
|
{
|
|
TryInitSendAsAddress();
|
|
}
|
|
|
|
// Send-as is applied recursively
|
|
foreach (FolderTreeNode desc in node.Descendants())
|
|
{
|
|
if (desc.SharedFolder != null)
|
|
{
|
|
desc.SharedFolder = desc.SharedFolder.WithFlagSendAsOwner(sendAs);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private string TryInitSendAsAddress()
|
|
{
|
|
// Initialise to the send-as address specified for an existing share, or a simple GAB lookup otherwise
|
|
string email =
|
|
_optionSendAsNodes[0].SharedFolder?.SendAsAddress ??
|
|
_featureSendAs?.FindSendAsAddress(_account, _optionSendAsNodes[0].AvailableFolder.Store);
|
|
|
|
if (string.IsNullOrEmpty(email))
|
|
{
|
|
// Try to initialise from the parent
|
|
TryInitSendAsAddressParent(_optionSendAsNodes[0]);
|
|
email = _optionSendAsNodes[0].SharedFolder?.SendAsAddress;
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(email))
|
|
{
|
|
// Set the entry field
|
|
textSendAsAddress.Text = email;
|
|
// And the value
|
|
_optionSendAsNodes[0].SharedFolder.SendAsAddress = email;
|
|
}
|
|
else if (checkSendAs.Checked)
|
|
{
|
|
if (SuppressInitialSendAsWarning)
|
|
{
|
|
SuppressInitialSendAsWarning = false;
|
|
}
|
|
else
|
|
{
|
|
MessageBox.Show(Properties.Resources.SharedFolders_SendAsFailed_Label,
|
|
Properties.Resources.SharedFolders_SendAsFailed_Title,
|
|
MessageBoxButtons.OK, MessageBoxIcon.Information);
|
|
}
|
|
}
|
|
return email;
|
|
}
|
|
|
|
private void TryInitSendAsAddressParent(FolderTreeNode node)
|
|
{
|
|
if (node == null)
|
|
return;
|
|
|
|
for (KTreeNode current = node.Parent; current is FolderTreeNode; current = current.Parent)
|
|
{
|
|
FolderTreeNode parentNode = (FolderTreeNode)current;
|
|
if (parentNode.SharedFolder == null)
|
|
break;
|
|
|
|
if (parentNode.SharedFolder.FlagSendAsOwner)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(parentNode.SharedFolder.SendAsAddress))
|
|
{
|
|
node.SharedFolder.SendAsAddress = parentNode.SharedFolder.SendAsAddress;
|
|
break;
|
|
}
|
|
}
|
|
else break;
|
|
}
|
|
}
|
|
|
|
private void EnableSendAsAddress()
|
|
{
|
|
// Hide unless there's only one selected, and send as is enabled
|
|
if (_optionSendAsNodes.Count == 1)
|
|
{
|
|
_labelSendAsAddress.Visible = textSendAsAddress.Visible = true;
|
|
_labelSendAsAddress.Enabled = textSendAsAddress.Enabled = OptionSendAs == CheckState.Checked;
|
|
}
|
|
else
|
|
{
|
|
_labelSendAsAddress.Visible = textSendAsAddress.Visible = false;
|
|
}
|
|
}
|
|
|
|
private void textSendAsAddress_TextChanged(object sender, EventArgs e)
|
|
{
|
|
for (int i = 0; i < _optionSendAsNodes.Count; ++i)
|
|
{
|
|
FolderTreeNode node = _optionSendAsNodes[i];
|
|
|
|
if (node.SharedFolder.SendAsAddress != textSendAsAddress.Text)
|
|
{
|
|
node.SharedFolder = node.SharedFolder.WithSendAsAddress(textSendAsAddress.Text);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void checkReminders_CheckedChanged(object sender, EventArgs e)
|
|
{
|
|
for (int i = 0; i < _optionRemindersNodes.Count; ++i)
|
|
{
|
|
FolderTreeNode node = _optionRemindersNodes[i];
|
|
bool reminders = false;
|
|
switch (checkReminders.CheckState)
|
|
{
|
|
case CheckState.Checked: reminders = true; break;
|
|
case CheckState.Indeterminate: reminders = _optionRemindersInitial[i]; break;
|
|
case CheckState.Unchecked: reminders = false; break;
|
|
}
|
|
|
|
if (node.SharedFolder.FlagCalendarReminders != reminders)
|
|
{
|
|
node.SharedFolder = node.SharedFolder.WithFlagCalendarReminders(reminders);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
}
|
|
}
|