kopano-ol-extension/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Controls/KCheckManager.cs

272 lines
10 KiB
C#

/// Copyright 2016 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 System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Acacia.Controls
{
public enum KCheckStyle
{
None,
TwoState,
ThreeState,
Recursive,
RecursiveThreeState
}
abstract public class KCheckManager
{
abstract public void SetCheck(KTreeNode node, CheckState state);
abstract public void ToggleCheck(KTreeNode node);
abstract public void ToggleCheck(IReadOnlyCollection<KTreeNode> nodes);
abstract public KCheckStyle CheckStyle { get; }
public class TwoState : KCheckManager
{
public override KCheckStyle CheckStyle { get { return KCheckStyle.TwoState; } }
public override void SetCheck(KTreeNode node, CheckState state)
{
node.CheckStateDirect = state == CheckState.Checked ? CheckState.Checked : CheckState.Unchecked;
}
public override void ToggleCheck(IReadOnlyCollection<KTreeNode> nodes)
{
// Do a majority vote to determine if we should
bool isChecked = nodes.Sum((x) => x.IsChecked ? 1 : 0) > (double)nodes.Count / 2.0;
foreach (KTreeNode node in nodes)
node.IsChecked = !isChecked;
}
public override void ToggleCheck(KTreeNode node)
{
node.CheckState = node.CheckState == CheckState.Checked ? CheckState.Unchecked : CheckState.Checked;
}
}
public class ThreeState : KCheckManager
{
public override KCheckStyle CheckStyle { get { return KCheckStyle.ThreeState; } }
public override void SetCheck(KTreeNode node, CheckState state)
{
node.CheckStateDirect = state;
}
public override void ToggleCheck(IReadOnlyCollection<KTreeNode> nodes)
{
// Count the check states
int[] counts = new int[3];
foreach (KTreeNode node in nodes)
{
++counts[(int)node.CheckState];
}
// Determine the current check state
CheckState state;
// Use indeterminate only if it has a clear majority
if (counts[(int)CheckState.Indeterminate] > counts[(int)CheckState.Checked] && counts[(int)CheckState.Indeterminate] > counts[(int)CheckState.Unchecked])
state = CheckState.Indeterminate;
else if (counts[(int)CheckState.Checked] > counts[(int)CheckState.Unchecked])
state = CheckState.Checked;
else
state = CheckState.Unchecked;
// Update the state
state = (CheckState)(((int)state + 1) % 3);
foreach (KTreeNode node in nodes)
node.CheckState = state;
}
public override void ToggleCheck(KTreeNode node)
{
node.CheckState = (CheckState)(((int)node.CheckState + 1) % 3);
}
}
public class Recursive : KCheckManager
{
public override KCheckStyle CheckStyle { get { return KCheckStyle.Recursive; } }
public override void SetCheck(KTreeNode node, CheckState state)
{
// TODO
node.CheckStateDirect = state;
}
public override void ToggleCheck(KTreeNode node)
{
try
{
// Set the check state recursively
node.Owner?.BeginUpdate();
SetNodeCheckState(node, NextCheckState(node.CheckState));
// Update the parent state
SetParentCheckState(node.Parent, node.CheckState);
}
finally
{
node.Owner?.EndUpdate();
}
}
protected virtual CheckState NextCheckState(CheckState checkState)
{
return (checkState == CheckState.Checked) ? CheckState.Unchecked : CheckState.Checked;
}
protected void SetParentCheckState(KTreeNode parent, CheckState childCheckState)
{
if (parent == null)
return;
if (childCheckState == CheckState.Indeterminate)
{
// An indeterminate node always leads to an indeterminate parent
parent.CheckState = CheckState.Indeterminate;
}
else
{
// Determine the check state
bool haveChecked = childCheckState == CheckState.Checked;
bool haveUnchecked = childCheckState == CheckState.Unchecked;
bool haveIndeterminate = childCheckState == CheckState.Indeterminate;
foreach (KTreeNode child in parent.Children)
{
if (child.CheckState == CheckState.Checked)
haveChecked = true;
else if (child.CheckState == CheckState.Unchecked)
haveUnchecked = true;
else
haveIndeterminate = true;
}
if (!haveIndeterminate && (haveChecked ^ haveUnchecked))
{
parent.CheckState = haveChecked ? CheckState.Checked : CheckState.Unchecked;
}
else
{
parent.CheckState = CheckState.Indeterminate;
}
}
SetParentCheckState(parent.Parent, parent.CheckState);
}
private void SetNodeCheckState(KTreeNode node, CheckState checkState)
{
// Apply the children first, otherwise the node's check state will be based on that again
foreach (KTreeNode child in node.Children)
SetNodeCheckState(child, checkState != CheckState.Indeterminate ? checkState : CheckState.Unchecked);
node.CheckState = checkState;
}
public override void ToggleCheck(IReadOnlyCollection<KTreeNode> nodes)
{
// Count the check states
int[] counts = new int[3];
foreach (KTreeNode node in nodes)
{
++counts[(int)node.CheckState];
}
// Sort by depth and remove any nodes whose ancestor is present, they'll get updated recursively
HashSet<KTreeNode> applyNodes = new HashSet<KTreeNode>();
foreach (KTreeNode node in nodes.OrderBy((x) => x.Depth))
{
bool add = true;
foreach (KTreeNode ancestor in node.Ancestors)
{
if (applyNodes.Contains(ancestor))
{
add = false;
}
break;
}
if (add)
applyNodes.Add(node);
}
// Determine the current check state
bool isChecked;
if (counts[(int)CheckState.Checked] > counts[(int)CheckState.Unchecked])
isChecked = true;
else
isChecked = false;
// Update the state for all the nodes
foreach (KTreeNode node in applyNodes)
SetNodeCheckState(node, isChecked ? CheckState.Unchecked : CheckState.Checked);
// Update the parents
foreach (KTreeNode node in applyNodes)
SetParentCheckState(node.Parent, node.CheckState);
}
}
public class RecursiveThreeState : Recursive
{
public override KCheckStyle CheckStyle { get { return KCheckStyle.RecursiveThreeState; } }
public override void SetCheck(KTreeNode node, CheckState state)
{
if (state == CheckState.Checked)
{
// Set indeterminate if any of the children is not checked
foreach (KTreeNode child in node.Children)
if (child.CheckState != CheckState.Checked)
{
state = CheckState.Indeterminate;
break;
}
}
else if (state == CheckState.Indeterminate)
{
if (node.Children.Count == 0)
state = CheckState.Checked;
}
node.CheckStateDirect = state;
SetParentCheckState(node.Parent, state);
}
protected override CheckState NextCheckState(CheckState checkState)
{
switch(checkState)
{
case CheckState.Unchecked:
return CheckState.Indeterminate;
case CheckState.Indeterminate:
return CheckState.Checked;
default:
return CheckState.Unchecked;
}
}
// TODO: special handling for multiple selection?
}
}
}