1
0
mirror of https://github.com/Kopano-dev/kopano-ol-extension.git synced 2023-10-10 13:37:40 +02:00

Initial import

This commit is contained in:
Patrick Simpson 2016-12-21 12:53:16 +01:00
parent e14319e74c
commit 4542b465c0
320 changed files with 49937 additions and 23 deletions

215
.gitignore vendored Normal file
View File

@ -0,0 +1,215 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
tmp/
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
*.VC.opendb
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
build/
bld/
[Bb]in/
[Oo]bj/
# Visual Studio 2015 cache/options directory
.vs/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# DNX
project.lock.json
artifacts/
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
*.VC.db
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
*.cachefile
# Visual Studio profiler
*.psess
*.vsp
*.vspx
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
_NCrunch_*
.*crunch*.local.xml
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
## TODO: Comment the next line if you want to checkin your
## web deploy settings but do note that will include unencrypted
## passwords
#*.pubxml
*.publishproj
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# Windows Azure Build Output
csx/
*.build.csdef
# Windows Store app package directory
AppPackages/
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
[Ss]tyle[Cc]op.*
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.publishsettings
node_modules/
orleans.codegen.cs
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# LightSwitch generated files
GeneratedArtifacts/
_Pvt_Extensions/
ModelManifest.xml

View File

@ -1,23 +0,0 @@
/***********************************************
* File : filename.source
* Project : Kopano OL Extension
* Descr : Description
*
* Created : 14.12.2016
*
* 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
************************************************/

View File

@ -0,0 +1,82 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25123.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AcaciaZPushPlugin", "AcaciaZPushPlugin\AcaciaZPushPlugin.csproj", "{1A7427A5-F814-4B07-98B2-C67D758B65D6}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Test", "Test", "{3BAA09C4-9E3C-4088-901C-E03489366468}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PluginUnitTests", "Test\PluginUnitTests\PluginUnitTests.csproj", "{1862C3CA-3347-4180-B076-D018BAA70F40}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OutlookIntegrationTests", "Test\OutlookIntegrationTests\OutlookIntegrationTests.csproj", "{A1AA144C-ABCE-462D-ADB4-6EC25AC062B7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PluginDebugger", "PluginDebugger\PluginDebugger.csproj", "{9258AD17-0A25-4669-A95C-93EC70882551}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{1A7427A5-F814-4B07-98B2-C67D758B65D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1A7427A5-F814-4B07-98B2-C67D758B65D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1A7427A5-F814-4B07-98B2-C67D758B65D6}.Debug|x64.ActiveCfg = Debug|Any CPU
{1A7427A5-F814-4B07-98B2-C67D758B65D6}.Debug|x64.Build.0 = Debug|Any CPU
{1A7427A5-F814-4B07-98B2-C67D758B65D6}.Debug|x86.ActiveCfg = Debug|Any CPU
{1A7427A5-F814-4B07-98B2-C67D758B65D6}.Debug|x86.Build.0 = Debug|Any CPU
{1A7427A5-F814-4B07-98B2-C67D758B65D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1A7427A5-F814-4B07-98B2-C67D758B65D6}.Release|Any CPU.Build.0 = Release|Any CPU
{1A7427A5-F814-4B07-98B2-C67D758B65D6}.Release|x64.ActiveCfg = Release|Any CPU
{1A7427A5-F814-4B07-98B2-C67D758B65D6}.Release|x64.Build.0 = Release|Any CPU
{1A7427A5-F814-4B07-98B2-C67D758B65D6}.Release|x86.ActiveCfg = Release|Any CPU
{1A7427A5-F814-4B07-98B2-C67D758B65D6}.Release|x86.Build.0 = Release|Any CPU
{1862C3CA-3347-4180-B076-D018BAA70F40}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1862C3CA-3347-4180-B076-D018BAA70F40}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1862C3CA-3347-4180-B076-D018BAA70F40}.Debug|x64.ActiveCfg = Debug|Any CPU
{1862C3CA-3347-4180-B076-D018BAA70F40}.Debug|x64.Build.0 = Debug|Any CPU
{1862C3CA-3347-4180-B076-D018BAA70F40}.Debug|x86.ActiveCfg = Debug|Any CPU
{1862C3CA-3347-4180-B076-D018BAA70F40}.Debug|x86.Build.0 = Debug|Any CPU
{1862C3CA-3347-4180-B076-D018BAA70F40}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1862C3CA-3347-4180-B076-D018BAA70F40}.Release|Any CPU.Build.0 = Release|Any CPU
{1862C3CA-3347-4180-B076-D018BAA70F40}.Release|x64.ActiveCfg = Release|Any CPU
{1862C3CA-3347-4180-B076-D018BAA70F40}.Release|x64.Build.0 = Release|Any CPU
{1862C3CA-3347-4180-B076-D018BAA70F40}.Release|x86.ActiveCfg = Release|Any CPU
{1862C3CA-3347-4180-B076-D018BAA70F40}.Release|x86.Build.0 = Release|Any CPU
{A1AA144C-ABCE-462D-ADB4-6EC25AC062B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A1AA144C-ABCE-462D-ADB4-6EC25AC062B7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A1AA144C-ABCE-462D-ADB4-6EC25AC062B7}.Debug|x64.ActiveCfg = Debug|Any CPU
{A1AA144C-ABCE-462D-ADB4-6EC25AC062B7}.Debug|x64.Build.0 = Debug|Any CPU
{A1AA144C-ABCE-462D-ADB4-6EC25AC062B7}.Debug|x86.ActiveCfg = Debug|Any CPU
{A1AA144C-ABCE-462D-ADB4-6EC25AC062B7}.Debug|x86.Build.0 = Debug|Any CPU
{A1AA144C-ABCE-462D-ADB4-6EC25AC062B7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A1AA144C-ABCE-462D-ADB4-6EC25AC062B7}.Release|Any CPU.Build.0 = Release|Any CPU
{A1AA144C-ABCE-462D-ADB4-6EC25AC062B7}.Release|x64.ActiveCfg = Release|Any CPU
{A1AA144C-ABCE-462D-ADB4-6EC25AC062B7}.Release|x64.Build.0 = Release|Any CPU
{A1AA144C-ABCE-462D-ADB4-6EC25AC062B7}.Release|x86.ActiveCfg = Release|Any CPU
{A1AA144C-ABCE-462D-ADB4-6EC25AC062B7}.Release|x86.Build.0 = Release|Any CPU
{9258AD17-0A25-4669-A95C-93EC70882551}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9258AD17-0A25-4669-A95C-93EC70882551}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9258AD17-0A25-4669-A95C-93EC70882551}.Debug|x64.ActiveCfg = Debug|Any CPU
{9258AD17-0A25-4669-A95C-93EC70882551}.Debug|x64.Build.0 = Debug|Any CPU
{9258AD17-0A25-4669-A95C-93EC70882551}.Debug|x86.ActiveCfg = Debug|Any CPU
{9258AD17-0A25-4669-A95C-93EC70882551}.Debug|x86.Build.0 = Debug|Any CPU
{9258AD17-0A25-4669-A95C-93EC70882551}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9258AD17-0A25-4669-A95C-93EC70882551}.Release|Any CPU.Build.0 = Release|Any CPU
{9258AD17-0A25-4669-A95C-93EC70882551}.Release|x64.ActiveCfg = Release|Any CPU
{9258AD17-0A25-4669-A95C-93EC70882551}.Release|x64.Build.0 = Release|Any CPU
{9258AD17-0A25-4669-A95C-93EC70882551}.Release|x86.ActiveCfg = Release|Any CPU
{9258AD17-0A25-4669-A95C-93EC70882551}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{1862C3CA-3347-4180-B076-D018BAA70F40} = {3BAA09C4-9E3C-4088-901C-E03489366468}
{A1AA144C-ABCE-462D-ADB4-6EC25AC062B7} = {3BAA09C4-9E3C-4088-901C-E03489366468}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,53 @@
/// 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;
namespace Acacia
{
/// <summary>
/// Attached to properties or classes to make them visible in the PluginDebugger.
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class)]
public class AcaciaOptionAttribute : Attribute
{
private string _description;
public string Description
{
get { return _description; }
}
/// <summary>
/// If specified, marker interface that controls presence of this option.
/// </summary>
public Type Interface
{
get;
set;
}
public AcaciaOptionAttribute(string description, Type marker = null)
{
this._description = description;
this.Interface = marker;
}
}
}

View File

@ -0,0 +1,612 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<!--
This section defines project-level properties.
AssemblyName
Name of the output assembly.
Configuration
Specifies a default value for debug.
OutputType
Must be "Library" for VSTO.
Platform
Specifies what CPU the output of this project can run on.
NoStandardLibraries
Set to "false" for VSTO.
RootNamespace
In C#, this specifies the namespace given to new files. In VB, all objects are
wrapped in this namespace at runtime.
-->
<PropertyGroup>
<ProjectTypeGuids>{BAA0C2D2-18E2-41B9-852F-F413020CAA33};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{1A7427A5-F814-4B07-98B2-C67D758B65D6}</ProjectGuid>
<OutputType>Library</OutputType>
<NoStandardLibraries>false</NoStandardLibraries>
<RootNamespace>Acacia</RootNamespace>
<AssemblyName>Kopano</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<DefineConstants>VSTO40</DefineConstants>
<BootstrapperEnabled>true</BootstrapperEnabled>
<PublishUrl>publish\</PublishUrl>
<InstallUrl />
<TargetCulture>en</TargetCulture>
<ApplicationVersion>1.0.0.0</ApplicationVersion>
<AutoIncrementApplicationRevision>true</AutoIncrementApplicationRevision>
<UpdateEnabled>true</UpdateEnabled>
<UpdateInterval>7</UpdateInterval>
<UpdateIntervalUnits>days</UpdateIntervalUnits>
<IsWebBootstrapper>False</IsWebBootstrapper>
<ProductName>Kopano OL Extension</ProductName>
<PublisherName>Kopano</PublisherName>
<SupportUrl />
<FriendlyName>Kopano OL Extension</FriendlyName>
<OfficeApplicationDescription>Kopano OL Extension</OfficeApplicationDescription>
<LoadBehavior>3</LoadBehavior>
</PropertyGroup>
<ItemGroup>
<BootstrapperPackage Include=".NETFramework,Version=v4.5.2">
<Visible>False</Visible>
<ProductName>Microsoft .NET Framework 4.5.2 %28x86 and x64%29</ProductName>
<Install>true</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
<Visible>False</Visible>
<ProductName>.NET Framework 3.5 SP1</ProductName>
<Install>false</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.VSTORuntime.4.0">
<Visible>False</Visible>
<ProductName>Microsoft Visual Studio 2010 Tools for Office Runtime %28x86 and x64%29</ProductName>
<Install>true</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Windows.Installer.4.5">
<Visible>False</Visible>
<ProductName>Windows Installer 4.5</ProductName>
<Install>true</Install>
</BootstrapperPackage>
</ItemGroup>
<PropertyGroup>
<!--
OfficeApplication
Add-in host application
-->
<OfficeApplication>Outlook</OfficeApplication>
</PropertyGroup>
<!--
This section defines properties that are set when the "Debug" configuration is selected.
DebugSymbols
If "true", create symbols (.pdb). If "false", do not create symbols.
DefineConstants
Constants defined for the preprocessor.
EnableUnmanagedDebugging
If "true", starting the debugger will attach both managed and unmanaged debuggers.
Optimize
If "true", optimize the build output. If "false", do not optimize.
OutputPath
Output path of project relative to the project file.
WarningLevel
Warning level for the compiler.
-->
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\Build\Debug\</OutputPath>
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
<DefineConstants>$(DefineConstants);DEBUG;TRACE</DefineConstants>
<WarningLevel>4</WarningLevel>
<RegisterForComInterop>false</RegisterForComInterop>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<!--
This section defines properties that are set when the "Release" configuration is selected.
DebugSymbols
If "true", create symbols (.pdb). If "false", do not create symbols.
DefineConstants
Constants defined for the preprocessor.
EnableUnmanagedDebugging
If "true", starting the debugger will attach both managed and unmanaged debuggers.
Optimize
If "true", optimize the build output. If "false", do not optimize.
OutputPath
Output path of project relative to the project file.
WarningLevel
Warning level for the compiler.
-->
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>..\Build\Release\</OutputPath>
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
<DefineConstants>VSTO40</DefineConstants>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<!--
This section specifies references for the project.
-->
<ItemGroup>
<Reference Include="Accessibility" />
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.2.3\lib\net45\NLog.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Drawing" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Web" />
<Reference Include="System.Web.Extensions" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xaml" />
<Reference Include="System.Xml" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="UIAutomationProvider" />
<Reference Include="WindowsBase" />
<Reference Include="WindowsFormsIntegration" />
</ItemGroup>
<ItemGroup>
<Reference Include="Microsoft.Office.Tools.v4.0.Framework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
<Reference Include="Microsoft.VisualStudio.Tools.Applications.Runtime, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
<Reference Include="Microsoft.Office.Tools, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
<Reference Include="Microsoft.Office.Tools.Common, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
<Reference Include="Microsoft.Office.Tools.Outlook, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
</ItemGroup>
<ItemGroup>
<Reference Include="Microsoft.Office.Tools.Common.v4.0.Utilities, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Office.Tools.Outlook.v4.0.Utilities, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>True</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<Reference Include="Office, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c">
<Private>False</Private>
<EmbedInteropTypes>true</EmbedInteropTypes>
</Reference>
<Reference Include="Microsoft.Office.Interop.Outlook, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c">
<Private>False</Private>
<EmbedInteropTypes>true</EmbedInteropTypes>
</Reference>
<Reference Include="stdole, Version=7.0.3300.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<Private>False</Private>
</Reference>
</ItemGroup>
<!--
This section defines the user source files that are part of the project.
A "Compile" element specifies a source file to compile.
An "EmbeddedResource" element specifies an .resx file for embedded resources.
A "None" element specifies a file that is not to be passed to the compiler (for instance,
a text file or XML file).
The "AppDesigner" element specifies the directory where the application properties files
can be found.
-->
<ItemGroup>
<Compile Include="AcaciaOptionAttribute.cs" />
<Compile Include="Config.cs" />
<Compile Include="Constants.cs" />
<Compile Include="Controls\KAnimator.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Controls\KBusyHider.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Controls\KBusyIndicator.cs">
<SubType>UserControl</SubType>
</Compile>
<Compile Include="Controls\KBusyIndicator.Designer.cs">
<DependentUpon>KBusyIndicator.cs</DependentUpon>
</Compile>
<Compile Include="Controls\KCheckManager.cs" />
<Compile Include="Controls\KCopyLabel.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Controls\KDialogButtons.cs">
<SubType>UserControl</SubType>
</Compile>
<Compile Include="Controls\KDialogButtons.Designer.cs">
<DependentUpon>KDialogButtons.cs</DependentUpon>
</Compile>
<Compile Include="Controls\KDialogNew.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Controls\KSelectionManager.cs" />
<Compile Include="Controls\KTreeNode.cs" />
<Compile Include="Controls\KTreeNodeLoader.cs" />
<Compile Include="Controls\KTreeNodes.cs" />
<Compile Include="Controls\KTree.cs">
<SubType>UserControl</SubType>
</Compile>
<Compile Include="Controls\KTreeNodeMeasurements.cs" />
<Compile Include="Controls\KTreeRenderer.cs" />
<Compile Include="Controls\KTreeRendererDefault.cs" />
<Compile Include="Controls\KTreeRendererVisualStyles.cs" />
<Compile Include="Controls\KUITask.cs" />
<Compile Include="Controls\KUIUtil.cs" />
<Compile Include="DebugOptions.cs" />
<Compile Include="Features\DebugSupport\AboutDialog.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Features\DebugSupport\AboutDialog.Designer.cs">
<DependentUpon>AboutDialog.cs</DependentUpon>
</Compile>
<Compile Include="Features\DebugSupport\DebugInfo.cs" />
<Compile Include="Features\DebugSupport\DebugSupportSettings.cs">
<SubType>UserControl</SubType>
</Compile>
<Compile Include="Features\DebugSupport\DebugSupportSettings.Designer.cs">
<DependentUpon>DebugSupportSettings.cs</DependentUpon>
</Compile>
<Compile Include="Features\DebugSupport\FeatureObjectConverter.cs" />
<Compile Include="Features\FeatureDisabled.cs" />
<Compile Include="Features\Features.cs" />
<Compile Include="Features\FeatureWithUI.cs" />
<Compile Include="Features\FreeBusy\FreeBusySettings.cs">
<SubType>UserControl</SubType>
</Compile>
<Compile Include="Features\FreeBusy\FreeBusySettings.Designer.cs">
<DependentUpon>FreeBusySettings.cs</DependentUpon>
</Compile>
<Compile Include="Features\GAB\GABInfo.cs" />
<Compile Include="Features\GAB\GABSettings.cs">
<SubType>UserControl</SubType>
</Compile>
<Compile Include="Features\GAB\GABSettings.Designer.cs">
<DependentUpon>GABSettings.cs</DependentUpon>
</Compile>
<Compile Include="Features\SendAs\FeatureSendAs.cs" />
<Compile Include="Features\SharedFolders\FolderTreeNode.cs" />
<Compile Include="GlobalOptions.cs" />
<Compile Include="Logging.cs" />
<Compile Include="OutlookConstants.cs" />
<Compile Include="UI\Outlook\OutlookImageList.cs" />
<Compile Include="UI\Outlook\RibbonToggleButton.cs" />
<Compile Include="UI\Outlook\RibbonButton.cs" />
<Compile Include="UI\Outlook\CommandElement.cs" />
<Compile Include="UI\Outlook\MenuItem.cs" />
<Compile Include="UI\Outlook\Types.cs" />
<Compile Include="Utils\RegistryUtil.cs" />
<Compile Include="ZPush\API\SharedFolders\AvailableFolder.cs" />
<Compile Include="ZPush\API\SharedFolders\SharedFolder.cs" />
<Compile Include="ZPush\API\SharedFolders\Types.cs" />
<Compile Include="ZPush\Connect\Soap\SoapException.cs" />
<Compile Include="ZPush\Connect\Soap\SoapParameters.cs" />
<Compile Include="ZPush\API\SharedFolders\SharedFoldersAPI.cs" />
<Compile Include="Features\SharedFolders\SharedFoldersDialog.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Features\SharedFolders\SharedFoldersDialog.Designer.cs">
<DependentUpon>SharedFoldersDialog.cs</DependentUpon>
</Compile>
<Compile Include="Features\SharedFolders\FeatureSharedFolders.cs" />
<Compile Include="Features\SharedFolders\StoreTreeNode.cs" />
<Compile Include="Features\WebApp\FeatureWebApp.cs" />
<Compile Include="Native\User32.cs" />
<Compile Include="Native\WM.cs" />
<Compile Include="ZPush\Connect\Soap\ISoapSerializable.cs" />
<Compile Include="ZPush\Connect\Soap\SoapSerializer.cs" />
<Compile Include="ZPush\Connect\Soap\SoapConstants.cs" />
<Compile Include="ZPush\Connect\ZPushRequestEncoder.cs" />
<Compile Include="ZPush\Connect\Soap\SoapRequestEncoder.cs" />
<Compile Include="ZPush\Connect\Soap\SoapRequest.cs" />
<Compile Include="Stubs\ItemType.cs" />
<Compile Include="Stubs\OutlookWrappers\DisposableWrapper.cs" />
<Compile Include="UI\FeatureSettings.cs">
<SubType>UserControl</SubType>
</Compile>
<Compile Include="UI\GABLookupControl.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="UI\GABLookupControl.Designer.cs">
<DependentUpon>GABLookupControl.cs</DependentUpon>
</Compile>
<Compile Include="UI\KopanoDialog.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="UI\SettingsDialog.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="UI\SettingsDialog.Designer.cs">
<DependentUpon>SettingsDialog.cs</DependentUpon>
</Compile>
<Compile Include="UI\SettingsPage.cs">
<SubType>UserControl</SubType>
</Compile>
<Compile Include="UI\SettingsPage.Designer.cs">
<DependentUpon>SettingsPage.cs</DependentUpon>
</Compile>
<Compile Include="UI\ErrorUtil.cs" />
<Compile Include="Utils\CollectionUtil.cs" />
<Compile Include="Utils\DnsUtil.cs" />
<Compile Include="Utils\FolderUtils.cs" />
<Compile Include="Utils\JSONUtils.cs" />
<Compile Include="Utils\OutlookRegistryUtils.cs" />
<Compile Include="Utils\ReflectUtil.cs" />
<Compile Include="Utils\StringUtil.cs" />
<Compile Include="Utils\Tasks.cs" />
<Compile Include="Utils\TasksBackground.cs" />
<Compile Include="Utils\TasksMainThread.cs" />
<Compile Include="Utils\TasksSynchronous.cs" />
<Compile Include="ZPush\Delegates.cs" />
<Compile Include="ZPush\FolderRegistration.cs" />
<Compile Include="ZPush\GABUser.cs" />
<Compile Include="ZPush\ItemsWatcher.cs" />
<Compile Include="ZPush\ZPushAccount.cs" />
<Compile Include="ZPush\ZPushAccounts.cs" />
<Compile Include="ZPush\ZPushChannel.cs" />
<Compile Include="ZPush\Connect\ZPushConnection.cs" />
<Compile Include="ZPush\ZPushFolder.cs" />
<Compile Include="ZPush\ZPushSync.cs" />
<Compile Include="ZPush\ZPushTypes.cs" />
<Compile Include="ZPush\ZPushWatcher.cs" />
<Compile Include="Features\DebugSupport\DebugDialog.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Features\DebugSupport\DebugDialog.Designer.cs">
<DependentUpon>DebugDialog.cs</DependentUpon>
</Compile>
<Compile Include="Features\DebugSupport\FeatureDebugSupport.cs" />
<Compile Include="Features\DebugSupport\Statistics.cs" />
<Compile Include="Features\Feature.cs" />
<Compile Include="Features\FreeBusy\FeatureFreeBusy.cs" />
<Compile Include="Features\FreeBusy\FreeBusyServer.cs" />
<Compile Include="Features\GAB\ChunkIndex.cs" />
<Compile Include="Features\GAB\FeatureGAB.cs" />
<Compile Include="Features\GAB\GABHandler.cs" />
<Compile Include="Features\Notes\FeatureNotes.cs" />
<Compile Include="Features\OutOfOffice\FeatureOutOfOffice.cs" />
<Compile Include="Features\OutOfOffice\OutOfOfficeDialog.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Features\OutOfOffice\OutOfOfficeDialog.Designer.cs">
<DependentUpon>OutOfOfficeDialog.cs</DependentUpon>
</Compile>
<Compile Include="Features\ReplyFlags\FeatureReplyFlags.cs" />
<Compile Include="Logger.cs" />
<Compile Include="NLogLogger.cs" />
<Compile Include="Stubs\IAddressBook.cs" />
<Compile Include="Stubs\IAppointmentItem.cs" />
<Compile Include="Stubs\IBase.cs" />
<Compile Include="Stubs\IContactItem.cs" />
<Compile Include="Stubs\IDistributionList.cs" />
<Compile Include="Stubs\IFolder.cs" />
<Compile Include="Stubs\IItem.cs" />
<Compile Include="Stubs\IMailItem.cs" />
<Compile Include="Stubs\ITaskItem.cs" />
<Compile Include="Stubs\INoteItem.cs" />
<Compile Include="Stubs\ISearch.cs" />
<Compile Include="Stubs\IStorageItem.cs" />
<Compile Include="Stubs\IUserProperty.cs" />
<Compile Include="Stubs\IZPushItem.cs" />
<Compile Include="Stubs\OutlookWrappers\AddressBookWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\DistributionListWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\AppointmentItemWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\ContactItemWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\FolderWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\MailItemWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\Mapping.cs" />
<Compile Include="Stubs\OutlookWrappers\TaskItemWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\NoteItemWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\OutlookWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\SearchWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\StorageItemWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\StoreWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\UserPropertyWrapper.cs" />
<Compile Include="Stubs\IStore.cs" />
<Compile Include="UI\ProgressDialog.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="UI\ProgressDialog.Designer.cs">
<DependentUpon>ProgressDialog.cs</DependentUpon>
</Compile>
<Compile Include="Utils\ActiveSync.cs" />
<Compile Include="Utils\ComRelease.cs" />
<Compile Include="Utils\LibUtils.cs" />
<Compile Include="Utils\MailEvents.cs" />
<Compile Include="Utils\PasswordEncryption.cs" />
<Compile Include="Properties\AssemblyInfo.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Features\ReplyFlags\ReplyFlags.cs" />
<Compile Include="UI\Outlook\OutlookUI.cs" />
<Compile Include="Utils\Util.cs" />
<Compile Include="ZPush\ZPushCapabilities.cs" />
<Compile Include="ZPush\ZPushLocalStore.cs" />
<Compile Include="ZPush\ZPushChannels.cs" />
<Compile Include="Version.cs" />
<Compile Include="WBXML\ActiveSync\ActiveSyncCodeSpace.cs" />
<Compile Include="WBXML\ActiveSync\AirNotifyCodePage.cs" />
<Compile Include="WBXML\ActiveSync\AirSyncBaseCodePage.cs" />
<Compile Include="WBXML\ActiveSync\AirSyncCodePage.cs" />
<Compile Include="WBXML\ActiveSync\CalendarCodePage.cs" />
<Compile Include="WBXML\ActiveSync\ComposeMailCodePage.cs" />
<Compile Include="WBXML\ActiveSync\Contacts2CodePage.cs" />
<Compile Include="WBXML\ActiveSync\ContactsCodePage.cs" />
<Compile Include="WBXML\ActiveSync\DocumentLibraryCodePage.cs" />
<Compile Include="WBXML\ActiveSync\Email2CodePage.cs" />
<Compile Include="WBXML\ActiveSync\EmailCodePage.cs" />
<Compile Include="WBXML\ActiveSync\FolderHierarchyCodePage.cs" />
<Compile Include="WBXML\ActiveSync\GALCodePage.cs" />
<Compile Include="WBXML\ActiveSync\ItemEstimateCodePage.cs" />
<Compile Include="WBXML\ActiveSync\ItemOperationsCodePage.cs" />
<Compile Include="WBXML\ActiveSync\MeetingResponseCodePage.cs" />
<Compile Include="WBXML\ActiveSync\MoveCodePage.cs" />
<Compile Include="WBXML\ActiveSync\NotesCodePage.cs" />
<Compile Include="WBXML\ActiveSync\PingCodePage.cs" />
<Compile Include="WBXML\ActiveSync\ProvisionCodePage.cs" />
<Compile Include="WBXML\ActiveSync\ResolveRecipientsCodePage.cs" />
<Compile Include="WBXML\ActiveSync\SearchCodePage.cs" />
<Compile Include="WBXML\ActiveSync\SettingsCodePage.cs" />
<Compile Include="WBXML\ActiveSync\TasksCodePage.cs" />
<Compile Include="WBXML\ActiveSync\ValidateCertCodePage.cs" />
<Compile Include="WBXML\AttributeCodePage.cs" />
<Compile Include="WBXML\AttributeCodeSpace.cs" />
<Compile Include="WBXML\AttributeStart.cs" />
<Compile Include="WBXML\AttributeValue.cs" />
<Compile Include="WBXML\EmptyCodePage.cs" />
<Compile Include="WBXML\EmptyCodeSpace.cs" />
<Compile Include="WBXML\GlobalTokens.cs" />
<Compile Include="WBXML\IANACharacterSets.cs" />
<Compile Include="WBXML\OpaqueDataExpression.cs" />
<Compile Include="WBXML\StringTable.cs" />
<Compile Include="WBXML\StringTableItem.cs" />
<Compile Include="WBXML\Tag.cs" />
<Compile Include="WBXML\TagCodePage.cs" />
<Compile Include="WBXML\TagCodeSpace.cs" />
<Compile Include="WBXML\WBXMLDocument.cs" />
<Compile Include="ZPush\Connect\ZPushWebService.cs" />
<EmbeddedResource Include="Controls\KBusyIndicator.resx">
<DependentUpon>KBusyIndicator.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Controls\KDialogButtons.resx">
<DependentUpon>KDialogButtons.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Controls\KDialogNew.resx">
<DependentUpon>KDialogNew.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Controls\KTree.resx">
<DependentUpon>KTree.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Features\DebugSupport\AboutDialog.resx">
<DependentUpon>AboutDialog.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Features\DebugSupport\DebugDialog.resx">
<DependentUpon>DebugDialog.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Features\DebugSupport\DebugSupportSettings.resx">
<DependentUpon>DebugSupportSettings.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Features\FreeBusy\FreeBusySettings.resx">
<DependentUpon>FreeBusySettings.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Features\GAB\GABSettings.resx">
<DependentUpon>GABSettings.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Features\OutOfOffice\OutOfOfficeDialog.resx">
<DependentUpon>OutOfOfficeDialog.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Features\SharedFolders\SharedFoldersDialog.resx">
<DependentUpon>SharedFoldersDialog.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
<DesignTime>True</DesignTime>
</Compile>
<EmbeddedResource Include="UI\ProgressDialog.resx">
<DependentUpon>ProgressDialog.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="UI\SettingsDialog.resx">
<DependentUpon>SettingsDialog.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="UI\SettingsPage.resx">
<DependentUpon>SettingsPage.cs</DependentUpon>
</EmbeddedResource>
<None Include="packages.config" />
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
</Compile>
<Compile Include="ThisAddIn.cs">
<SubType>Code</SubType>
</Compile>
<None Include="ThisAddIn.Designer.xml">
<DependentUpon>ThisAddIn.cs</DependentUpon>
</None>
<Compile Include="ThisAddIn.Designer.cs">
<DependentUpon>ThisAddIn.Designer.xml</DependentUpon>
</Compile>
<AppDesigner Include="Properties\" />
</ItemGroup>
<ItemGroup>
<WCFMetadata Include="Service References\" />
</ItemGroup>
<ItemGroup>
<None Include="Resources\Kopano.ico" />
</ItemGroup>
<ItemGroup>
<None Include="Properties\TreeLoading.gif" />
<None Include="Resources\Icons\Ribbon_About.png" />
<None Include="Resources\Icons\Ribbon_About_Small.png" />
<Content Include="Resources\Icons\Ribbon_AddSharedFolder.png" />
<Content Include="Resources\Icons\Ribbon_AddSharedFolder_Small.png" />
<Content Include="Resources\Icons\Ribbon_Debug.png" />
<Content Include="Resources\Icons\Ribbon_Debug_Small.png" />
<Content Include="Resources\Icons\Ribbon_Logfile.png" />
<Content Include="Resources\Icons\Ribbon_Logfile_Small.png" />
<Content Include="Resources\Icons\Ribbon_ManageSharedFolders.png" />
<None Include="Resources\Icons\Ribbon_ManageSharedFolders1.png" />
<Content Include="Resources\Icons\Ribbon_ManageSharedFolders_Small.png" />
<None Include="Resources\Icons\Ribbon_ManageSharedFolders_Small1.png" />
<None Include="Resources\Icons\Ribbon_ManageSharedFolders_Small11.png" />
<Content Include="Resources\Icons\Ribbon_MDM.png" />
<Content Include="Resources\Icons\Ribbon_MDM_Small.png" />
<Content Include="Resources\Icons\Ribbon_OOF.png" />
<Content Include="Resources\Icons\Ribbon_OOF_Small.png" />
<Content Include="Resources\Icons\Ribbon_Restore.png" />
<Content Include="Resources\Icons\Ribbon_Restore_Small.png" />
<Content Include="Resources\Icons\Ribbon_Rules.png" />
<Content Include="Resources\Icons\Ribbon_Rules_Small.png" />
<Content Include="Resources\Icons\Ribbon_Settings.png" />
<Content Include="Resources\Icons\Ribbon_Settings_Small.png" />
<Content Include="Resources\Icons\Ribbon_SyncGAB.png" />
<Content Include="Resources\Icons\Ribbon_SyncGAB_Small.png" />
<Content Include="Resources\Icons\Ribbon_WebApp.png" />
<Content Include="Resources\Icons\Ribbon_WebApp_Small.png" />
<Content Include="Resources\Icons\Ribbon_WebMeetings.png" />
<Content Include="Resources\Icons\Ribbon_WebMeetings_Small.png" />
</ItemGroup>
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<PropertyGroup>
<SignManifests>true</SignManifests>
</PropertyGroup>
<PropertyGroup>
<ManifestKeyFile>TemporaryKey.pfx</ManifestKeyFile>
</PropertyGroup>
<PropertyGroup>
<ManifestCertificateThumbprint>BBFB28C253605E59B6EFA7AAC079FB30F06C8298</ManifestCertificateThumbprint>
</PropertyGroup>
<PropertyGroup>
<SignAssembly>false</SignAssembly>
</PropertyGroup>
<!-- Include the build rules for a C# project. -->
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- Include additional build rules for an Office application add-in. -->
<Import Project="$(VSToolsPath)\OfficeTools\Microsoft.VisualStudio.Tools.Office.targets" Condition="'$(VSToolsPath)' != ''" />
<!-- This section defines VSTO properties that describe the host-changeable project properties. -->
<ProjectExtensions>
<VisualStudio>
<FlavorProperties GUID="{BAA0C2D2-18E2-41B9-852F-F413020CAA33}">
<ProjectProperties HostName="Outlook" HostPackage="{29A7B9D7-A7F1-4328-8EF0-6B2D1A56B2C1}" OfficeVersion="15.0" VstxVersion="4.0" ApplicationType="Outlook" Language="cs" TemplatesPath="" DebugInfoExeName="#Software\Microsoft\Office\16.0\Outlook\InstallRoot\Path#outlook.exe" AddItemTemplatesGuid="{A58A78EB-1C92-4DDD-80CF-E8BD872ABFC4}" />
<Host Name="Outlook" GeneratedCodeNamespace="Acacia" IconIndex="0">
<HostItem Name="ThisAddIn" Code="ThisAddIn.cs" CanonicalName="AddIn" CanActivate="false" IconIndex="1" Blueprint="ThisAddIn.Designer.xml" GeneratedCode="ThisAddIn.Designer.cs" />
</Host>
</FlavorProperties>
</VisualStudio>
</ProjectExtensions>
</Project>

View File

@ -0,0 +1,29 @@
/// 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;
namespace Acacia
{
public static class Config
{
public const int ACCOUNT_CHECK_INTERVAL = 5000;
}
}

View File

@ -0,0 +1,99 @@
/// 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.Text.RegularExpressions;
using System.Threading.Tasks;
namespace Acacia
{
public static class Constants
{
#region Reply flags
public const string ZPUSH_REPLY_HEADER = OutlookConstants.NS_TRANSPORT_MESSAGE_HEADERS + "X-Push-Flags";
public const string ZPUSH_REPLY_CATEGORY_PREFIX = "Push: Email ";
public const string ZPUSH_REPLY_CATEGORY_REPLIED = "replied";
public const string ZPUSH_REPLY_CATEGORY_REPLIED_TO_ALL = "replied-to-all";
public const string ZPUSH_REPLY_CATEGORY_FORWARDED = "forwarded";
public readonly static Regex ZPUSH_REPLY_CATEGORY_REGEX = new Regex("([a-zA-Z\\-]+) on (.* GMT)$");
#endregion
#region SendAs
public const string ZPUSH_SEND_AS = OutlookConstants.NS_TRANSPORT_MESSAGE_HEADERS + "X-Push-Sender";
public const string ZPUSH_SEND_AS_NAME = OutlookConstants.NS_TRANSPORT_MESSAGE_HEADERS + "X-Push-Sender-Name";
#endregion
#region GAB
public const string ZPUSH_GAB_INDEX = "$PushIndex";
public const int ZPUSH_GAB_NEWEST_MAX_CHECK = 5;
#endregion
#region Product names
public const string PRODUCT_PREFIX = "Kopano";
public const string PRODUCT_NAME = "Kopano OL Extension";
#endregion
#region Local stores
public const string LOCAL_STORE_DEFAULT_DIRECTORY = "%LocalAppData%\\" + PRODUCT_PREFIX + "\\" + PRODUCT_NAME;
public const string LOCAL_STORE_FILENAME = PRODUCT_PREFIX + "LocalFolders";
public const string LOCAL_STORE_EXTENSION = "pst";
#endregion
#region ActiveSync headers
public const string ZPUSH_HEADER_GAB_NAME = "X-Push-GAB-Name";
public const string ZPUSH_HEADER_CAPABILITIES = "X-Push-Capabilities";
public const string ZPUSH_HEADER_PLUGIN = "X-Push-Plugin";
public const string ZPUSH_HEADER_VERSION = "X-Z-Push-Version";
#endregion
#region Capabilities
public const string ZPUSH_CAPABILITY_NOTES = "notes";
public const string ZPUSH_CAPABILITY_OUT_OF_OFFICE = "oof";
public const string ZPUSH_CAPABILITY_OUT_OF_OFFICE_TIMES = "ooftime";
#endregion
public const string DATE_ISO_8601 = "yyyyMMddTHHmmssZ";
public static readonly TimeSpan ZPUSH_SYNC_DEFAULT_PERIOD = new TimeSpan(1, 0, 0);
#region Registry
public const string PLUGIN_REGISTRY_BASE = "Software\\" + PRODUCT_PREFIX + "\\" + PRODUCT_NAME;
public const string PLUGIN_REGISTRY_LOGLEVEL = "LogLevel";
#endregion
}
}

View File

@ -0,0 +1,96 @@
/// 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.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Acacia.Controls
{
public class KAnimator : PictureBox
{
public KAnimator()
{
BackColor = Color.Transparent;
SizeMode = PictureBoxSizeMode.Zoom;
}
private Image _stillFrame;
private Image _animation;
private bool _animating;
public Image Animation
{
get { return _animation; }
set
{
if (_animation != value)
{
_animation = value;
_stillFrame = value;
if (_animation != null)
{
int frameCount = _animation.GetFrameCount(FrameDimension.Time);
if (frameCount > 0)
{
_animation.SelectActiveFrame(FrameDimension.Time, 0);
_stillFrame = new Bitmap(_animation);
}
}
SetImage();
}
}
}
public bool Animate
{
get { return _animating; }
set
{
if (_animating != value)
{
_animating = value;
SetImage();
}
}
}
private void SetImage()
{
if (_animating)
Image = _animation;
else
// TODO: would be awesome to finish the animation
Image = _stillFrame;
}
public override Size GetPreferredSize(Size proposedSize)
{
Size sz = base.GetPreferredSize(proposedSize);
// Scale for high resolution screens.
using (Graphics g = CreateGraphics())
sz = sz.ScaleDpi(g);
return sz;
}
}
}

View File

@ -0,0 +1,189 @@
/// 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.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Acacia.Controls
{
public class KBusyHider : Panel, KUITaskProgress
{
#region UI
private KBusyIndicator _busyOverlay = null;
private KBusyIndicator _completeOverlay = null;
private string _busyText;
public bool Busy
{
get
{
return _busyOverlay != null;
}
set
{
if (value == true)
{
if (_busyOverlay != null)
return;
_busyOverlay = CreateOverlay(_busyText, true);
}
else if (_busyOverlay != null)
{
// Remove the overlay
RemoveOverlay(_busyOverlay);
_busyOverlay = null;
}
}
}
private void RemoveOverlay(KBusyIndicator overlay)
{
Controls.Remove(overlay);
// And enable the controls
foreach (Control control in Controls)
control.Enabled = true;
}
private KBusyIndicator CreateOverlay(string text, bool showProgress)
{
try
{
SuspendLayout();
// Create a new busy indicator and layouyt
KBusyIndicator overlay = new KBusyIndicator();
overlay.ShowProgress = showProgress;
overlay.Text = text;
// Remove the existing controls; the overlay must be first to be rendered on top,
// and there's no insert function.
// Also disable the controls on the fly
List<Control> existing = new List<Control>();
while (Controls.Count > 0)
{
existing.Add(Controls[0]);
Controls[0].Enabled = false;
Controls.RemoveAt(0);
}
// Add the busy overlay
Controls.Add(overlay);
// Re-add the existing controls
Controls.AddRange(existing.ToArray());
return overlay;
}
finally
{
ResumeLayout();
}
}
public override string Text
{
get
{
return BusyText;
}
set
{
BusyText = value;
}
}
public string BusyText
{
get
{
return _busyText;
}
set
{
_busyText = value;
if (_busyOverlay != null)
_busyOverlay.Text = _busyText;
}
}
public void ShowCompletion(string text)
{
// Show the overlay
_completeOverlay = CreateOverlay(text, false);
_completeOverlay.MouseMove += _completeOverlay_MouseMove;
// Add a timer to hide
var timer = new System.Windows.Forms.Timer();
timer.Interval = 5000; // TODO: make a property for this
timer.Tick += (o, args) =>
{
timer.Stop();
HideCompleteOverlay();
};
timer.Start();
}
private void _completeOverlay_MouseMove(object sender, MouseEventArgs e)
{
HideCompleteOverlay();
}
private void HideCompleteOverlay()
{
if (_completeOverlay != null)
{
RemoveOverlay(_completeOverlay);
_completeOverlay = null;
}
}
protected override void OnLayout(LayoutEventArgs levent)
{
base.OnLayout(levent);
foreach (Control control in Controls)
{
if (control is KBusyIndicator)
{
Size pref = control.GetPreferredSize(ClientSize);
control.Bounds = ClientRectangle.Center(pref);
}
}
}
public CancellationTokenSource Cancellation
{
get;
set;
}
#endregion
}
}

View File

@ -0,0 +1,104 @@
namespace Acacia.Controls
{
partial class KBusyIndicator
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this._layout = new System.Windows.Forms.TableLayoutPanel();
this._text = new System.Windows.Forms.Label();
this._progress = new System.Windows.Forms.ProgressBar();
this._layout.SuspendLayout();
this.SuspendLayout();
//
// _layout
//
this._layout.AutoSize = true;
this._layout.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
this._layout.ColumnCount = 1;
this._layout.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
this._layout.Controls.Add(this._text, 0, 0);
this._layout.Controls.Add(this._progress, 0, 1);
this._layout.Dock = System.Windows.Forms.DockStyle.Fill;
this._layout.Location = new System.Drawing.Point(15, 15);
this._layout.Margin = new System.Windows.Forms.Padding(15, 15, 15, 15);
this._layout.Name = "_layout";
this._layout.Padding = new System.Windows.Forms.Padding(9, 9, 9, 9);
this._layout.RowCount = 2;
this._layout.RowStyles.Add(new System.Windows.Forms.RowStyle());
this._layout.RowStyles.Add(new System.Windows.Forms.RowStyle());
this._layout.Size = new System.Drawing.Size(231, 115);
this._layout.TabIndex = 0;
//
// _text
//
this._text.AutoSize = true;
this._text.Dock = System.Windows.Forms.DockStyle.Fill;
this._text.Location = new System.Drawing.Point(15, 9);
this._text.Margin = new System.Windows.Forms.Padding(6, 0, 6, 0);
this._text.Name = "_text";
this._text.Size = new System.Drawing.Size(201, 25);
this._text.TabIndex = 0;
this._text.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// _progress
//
this._progress.Dock = System.Windows.Forms.DockStyle.Fill;
this._progress.Location = new System.Drawing.Point(24, 49);
this._progress.Margin = new System.Windows.Forms.Padding(15, 15, 15, 15);
this._progress.MarqueeAnimationSpeed = 50;
this._progress.Name = "_progress";
this._progress.Size = new System.Drawing.Size(183, 42);
this._progress.Style = System.Windows.Forms.ProgressBarStyle.Marquee;
this._progress.TabIndex = 1;
//
// KBusyIndicator
//
this.AutoScaleDimensions = new System.Drawing.SizeF(11F, 24F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.AutoSize = true;
this.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
this.BackColor = System.Drawing.SystemColors.Control;
this.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.Controls.Add(this._layout);
this.Margin = new System.Windows.Forms.Padding(6, 6, 6, 6);
this.Name = "KBusyIndicator";
this.Padding = new System.Windows.Forms.Padding(15, 15, 15, 15);
this.Size = new System.Drawing.Size(261, 145);
this._layout.ResumeLayout(false);
this._layout.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.TableLayoutPanel _layout;
private System.Windows.Forms.Label _text;
private System.Windows.Forms.ProgressBar _progress;
}
}

View File

@ -0,0 +1,62 @@
/// 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.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Acacia.Controls
{
public partial class KBusyIndicator : UserControl
{
public KBusyIndicator()
{
InitializeComponent();
}
private bool _showProgress = true;
public bool ShowProgress
{
get { return _showProgress; }
set
{
_showProgress = value;
_progress.Visible = _showProgress;
_text.Padding = _showProgress ? new Padding(15) : new Padding(15, 30, 15, 30);
}
}
public override string Text
{
get
{
return _text.Text;
}
set
{
_text.Text = value;
}
}
}
}

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -0,0 +1,271 @@
/// 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?
}
}
}

View File

@ -0,0 +1,35 @@
/// 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 class KCopyLabel : TextBox
{
public KCopyLabel()
{
ReadOnly = true;
BorderStyle = BorderStyle.None;
TabStop = false;
}
}
}

View File

@ -0,0 +1,78 @@
namespace Acacia.Controls
{
partial class KDialogButtons
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(KDialogButtons));
this.buttonApply = new System.Windows.Forms.Button();
this.buttonCancel = new System.Windows.Forms.Button();
this.buttonClose = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// buttonApply
//
resources.ApplyResources(this.buttonApply, "buttonApply");
this.buttonApply.Name = "buttonApply";
this.buttonApply.UseVisualStyleBackColor = true;
this.buttonApply.Click += new System.EventHandler(this.buttonApply_Click);
//
// buttonCancel
//
resources.ApplyResources(this.buttonCancel, "buttonCancel");
this.buttonCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.buttonCancel.Name = "buttonCancel";
this.buttonCancel.UseVisualStyleBackColor = true;
this.buttonCancel.Click += new System.EventHandler(this.buttonCancel_Click);
//
// buttonClose
//
resources.ApplyResources(this.buttonClose, "buttonClose");
this.buttonClose.DialogResult = System.Windows.Forms.DialogResult.OK;
this.buttonClose.Name = "buttonClose";
this.buttonClose.UseVisualStyleBackColor = true;
this.buttonClose.Click += new System.EventHandler(this.buttonClose_Click);
//
// KDialogButtons
//
resources.ApplyResources(this, "$this");
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.buttonApply);
this.Controls.Add(this.buttonCancel);
this.Controls.Add(this.buttonClose);
this.Name = "KDialogButtons";
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Button buttonApply;
private System.Windows.Forms.Button buttonCancel;
private System.Windows.Forms.Button buttonClose;
}
}

View File

@ -0,0 +1,220 @@
/// 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.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;
namespace Acacia.Controls
{
public partial class KDialogButtons : UserControl
{
private static readonly Padding DefaultButtonPadding = new Padding(12, 6, 12, 6);
private static readonly Padding DefaultButtonMargin = new Padding(6, 0, 6, 0);
public KDialogButtons()
{
InitializeComponent();
ButtonPadding = DefaultButtonPadding;
ButtonMargin = DefaultButtonMargin;
CheckButtons();
}
private bool _isDirty;
/// <summary>
/// Dirty flag. Enables the Apply button and prevents closing without confirmation
/// </summary>
[Category("Kopano")]
public bool IsDirty
{
get { return _isDirty; }
set { _isDirty = value; CheckButtons(); }
}
private bool _hasApply = true;
/// <summary>
/// Shows or hides the apply button.
/// </summary>
[Category("Kopano")]
public bool HasApply
{
get { return _hasApply; }
set { _hasApply = value; CheckButtons(); }
}
private void CheckButtons()
{
SuspendLayout();
buttonApply.Enabled = IsDirty;
buttonApply.Visible = _hasApply;
buttonClose.Visible = !IsDirty || !_hasApply;
buttonCancel.Visible = IsDirty && !_hasApply;
KDialogNew dlg = FindForm() as KDialogNew;
if (dlg != null)
{
dlg.AcceptButton = buttonApply;
dlg.CancelButton = IsDirty ? buttonCancel : buttonClose;
}
ResumeLayout();
}
protected override void OnParentVisibleChanged(EventArgs e)
{
base.OnParentVisibleChanged(e);
CheckButtons();
}
private Padding _buttonPadding;
[Category("Kopano")]
public Padding ButtonPadding
{
get { return _buttonPadding; }
set
{
_buttonPadding = value;
foreach (Control child in Controls)
child.Padding = _buttonPadding;
}
}
bool ShouldSerializeButtonPadding() { return ButtonPadding != DefaultButtonPadding; }
private Padding _buttonMargin;
[Category("Kopano")]
public Padding ButtonMargin
{
get { return _buttonMargin; }
set
{
_buttonMargin = value;
foreach (Control child in Controls)
child.Margin = _buttonMargin;
}
}
bool ShouldSerializeButtonMargin() { return ButtonMargin != DefaultButtonMargin; }
private Size? _buttonSize;
[Category("Kopano")]
public Size? ButtonSize
{
get { return _buttonSize; }
set { _buttonSize = value; }
}
protected override void OnControlAdded(ControlEventArgs e)
{
base.OnControlAdded(e);
e.Control.Padding = _buttonPadding;
e.Control.Margin = _buttonMargin;
}
protected override void OnLayout(LayoutEventArgs e)
{
base.OnLayout(e);
Size buttonSize = CalcButtonSize();
int x = ClientSize.Width;
int y = Padding.Top;
foreach (Control child in Controls.Cast<Control>().OrderByDescending(ctrl => ctrl.TabIndex))
{
if (child.Visible)
{
child.SetBounds(x - buttonSize.Width + child.Margin.Left,
y + child.Margin.Top,
buttonSize.Width - child.Margin.Horizontal,
buttonSize.Height - child.Margin.Vertical);
x -= buttonSize.Width;
}
}
}
private Size CalcButtonSize()
{
Size buttonSize;
if (this._buttonSize.HasValue)
{
buttonSize = new Size(this._buttonSize.Value.Width + ButtonMargin.Horizontal,
this._buttonSize.Value.Height + ButtonMargin.Vertical);
}
else
{
// Make all buttons the size of the largest one
buttonSize = new Size();
foreach (Control child in Controls)
{
Size childSize = child.GetPreferredSize(ClientSize);
buttonSize = new Size(Math.Max(buttonSize.Width, childSize.Width + child.Margin.Horizontal),
Math.Max(buttonSize.Height, childSize.Height + child.Margin.Vertical));
}
}
return buttonSize;
}
public override Size GetPreferredSize(Size proposedSize)
{
Size buttonSize = CalcButtonSize();
int count = Controls.Cast<Control>().Count(x => x.Visible);
return new Size(buttonSize.Width * count + Padding.Horizontal, buttonSize.Height + Padding.Vertical);
}
private void buttonApply_Click(object sender, EventArgs e)
{
if (IsDirty)
OnApply();
}
virtual protected void OnApply()
{
if (Apply != null)
Apply(this, new EventArgs());
}
[Category("Kopano")]
public event EventHandler Apply;
public CancellationTokenSource Cancellation
{
get;
set;
}
private void buttonCancel_Click(object sender, EventArgs e)
{
if (Cancellation != null)
Cancellation.Cancel();
}
private void buttonClose_Click(object sender, EventArgs e)
{
if (Cancellation != null)
Cancellation.Cancel();
}
}
}

View File

@ -0,0 +1,258 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="mscorlib" name="mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="buttonApply.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="buttonApply.AutoSizeMode" type="System.Windows.Forms.AutoSizeMode, System.Windows.Forms">
<value>GrowAndShrink</value>
</data>
<data name="buttonApply.ImeMode" type="System.Windows.Forms.ImeMode, System.Windows.Forms">
<value>NoControl</value>
</data>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="buttonApply.Location" type="System.Drawing.Point, System.Drawing">
<value>3, 3</value>
</data>
<data name="buttonApply.Padding" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>3, 3, 3, 3</value>
</data>
<data name="buttonApply.Size" type="System.Drawing.Size, System.Drawing">
<value>49, 29</value>
</data>
<data name="buttonApply.TabIndex" type="System.Int32, mscorlib">
<value>0</value>
</data>
<data name="buttonApply.Text" xml:space="preserve">
<value>Apply</value>
</data>
<data name="&gt;&gt;buttonApply.Name" xml:space="preserve">
<value>buttonApply</value>
</data>
<data name="&gt;&gt;buttonApply.Type" xml:space="preserve">
<value>System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;buttonApply.Parent" xml:space="preserve">
<value>$this</value>
</data>
<data name="&gt;&gt;buttonApply.ZOrder" xml:space="preserve">
<value>0</value>
</data>
<data name="buttonCancel.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="buttonCancel.AutoSizeMode" type="System.Windows.Forms.AutoSizeMode, System.Windows.Forms">
<value>GrowAndShrink</value>
</data>
<data name="buttonCancel.ImeMode" type="System.Windows.Forms.ImeMode, System.Windows.Forms">
<value>NoControl</value>
</data>
<data name="buttonCancel.Location" type="System.Drawing.Point, System.Drawing">
<value>161, 3</value>
</data>
<data name="buttonCancel.Padding" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>3, 3, 3, 3</value>
</data>
<data name="buttonCancel.Size" type="System.Drawing.Size, System.Drawing">
<value>56, 29</value>
</data>
<data name="buttonCancel.TabIndex" type="System.Int32, mscorlib">
<value>1</value>
</data>
<data name="buttonCancel.Text" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="buttonCancel.Visible" type="System.Boolean, mscorlib">
<value>False</value>
</data>
<data name="&gt;&gt;buttonCancel.Name" xml:space="preserve">
<value>buttonCancel</value>
</data>
<data name="&gt;&gt;buttonCancel.Type" xml:space="preserve">
<value>System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;buttonCancel.Parent" xml:space="preserve">
<value>$this</value>
</data>
<data name="&gt;&gt;buttonCancel.ZOrder" xml:space="preserve">
<value>1</value>
</data>
<data name="buttonClose.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="buttonClose.AutoSizeMode" type="System.Windows.Forms.AutoSizeMode, System.Windows.Forms">
<value>GrowAndShrink</value>
</data>
<data name="buttonClose.ImeMode" type="System.Windows.Forms.ImeMode, System.Windows.Forms">
<value>NoControl</value>
</data>
<data name="buttonClose.Location" type="System.Drawing.Point, System.Drawing">
<value>82, 3</value>
</data>
<data name="buttonClose.Padding" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>3, 3, 3, 3</value>
</data>
<data name="buttonClose.Size" type="System.Drawing.Size, System.Drawing">
<value>49, 29</value>
</data>
<data name="buttonClose.TabIndex" type="System.Int32, mscorlib">
<value>2</value>
</data>
<data name="buttonClose.Text" xml:space="preserve">
<value>Close</value>
</data>
<data name="&gt;&gt;buttonClose.Name" xml:space="preserve">
<value>buttonClose</value>
</data>
<data name="&gt;&gt;buttonClose.Type" xml:space="preserve">
<value>System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;buttonClose.Parent" xml:space="preserve">
<value>$this</value>
</data>
<data name="&gt;&gt;buttonClose.ZOrder" xml:space="preserve">
<value>2</value>
</data>
<metadata name="$this.Localizable" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<data name="$this.AutoScaleDimensions" type="System.Drawing.SizeF, System.Drawing">
<value>6, 13</value>
</data>
<data name="$this.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="$this.AutoSizeMode" type="System.Windows.Forms.AutoSizeMode, System.Windows.Forms">
<value>GrowAndShrink</value>
</data>
<data name="$this.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>2, 2, 2, 2</value>
</data>
<data name="$this.Size" type="System.Drawing.Size, System.Drawing">
<value>220, 35</value>
</data>
<data name="&gt;&gt;$this.Name" xml:space="preserve">
<value>KDialogButtons</value>
</data>
<data name="&gt;&gt;$this.Type" xml:space="preserve">
<value>System.Windows.Forms.UserControl, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
</root>

View File

@ -0,0 +1,131 @@
/// 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.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Acacia.Controls
{
public class KDialogNew : Form, KUITaskProgress
{
public KDialogNew()
{
Icon = Properties.Resources.Kopano;
}
#region Control links
[Category("Kopano")]
public KDialogButtons DialogButtons
{
get;
set;
}
[Category("Kopano")]
public KBusyHider BusyHider
{
get;
set;
}
#endregion
#region KUITaskProgress
// TODO: if BusyHider is not set, pop up dialogs
public string BusyText
{
get { return BusyHider?.BusyText; }
set { if (BusyHider != null) BusyHider.BusyText = value; }
}
public bool Busy
{
get
{
if (BusyHider == null)
return false;
return BusyHider.Busy;
}
set
{
if (BusyHider != null)
BusyHider.Busy = value;
}
}
public void ShowCompletion(string text)
{
if (BusyHider != null)
BusyHider.ShowCompletion(text);
}
public CancellationTokenSource Cancellation
{
get { return DialogButtons?.Cancellation; }
set { if (DialogButtons != null) DialogButtons.Cancellation = value; }
}
#endregion
#region Form closing
protected override void OnFormClosing(FormClosingEventArgs e)
{
base.OnFormClosing(e);
// If we have dialog buttons, check if the dirty flag is set
if (DialogButtons != null && DialogButtons.IsDirty)
{
OnDirtyFormClosing(e);
}
}
/// <summary>
/// Event that is raised only when trying to close a dirty form
/// </summary>
[Category("Kopano")]
public event FormClosingEventHandler DirtyFormClosing;
virtual protected void OnDirtyFormClosing(FormClosingEventArgs e)
{
if (DirtyFormClosing != null)
DirtyFormClosing(this, e);
}
#endregion
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(KDialogNew));
this.SuspendLayout();
//
// KDialogNew
//
resources.ApplyResources(this, "$this");
this.Name = "KDialogNew";
this.ResumeLayout(false);
}
}
}

View File

@ -0,0 +1,133 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="$this.Localizable" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="$this.ClientSize" type="System.Drawing.Size, System.Drawing">
<value>284, 261</value>
</data>
<data name="&gt;&gt;$this.Name" xml:space="preserve">
<value>KDialogNew</value>
</data>
<data name="&gt;&gt;$this.Type" xml:space="preserve">
<value>System.Windows.Forms.Form, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
</root>

View File

@ -0,0 +1,100 @@
/// 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;
namespace Acacia.Controls
{
abstract public class KSelectionManager
{
abstract public IReadOnlyCollection<KTreeNode> CurrentSelection { get; }
abstract public void Clear();
abstract public void Add(KTreeNode node);
abstract public void Toggle(KTreeNode node);
public class Single : KSelectionManager
{
private KTreeNode _selectedNode;
public override IReadOnlyCollection<KTreeNode> CurrentSelection
{
get
{
List<KTreeNode> sel = new List<KTreeNode>();
if (_selectedNode != null)
sel.Add(_selectedNode);
return sel;
}
}
public override void Clear()
{
_selectedNode = null;
}
public override void Add(KTreeNode node)
{
_selectedNode = node;
}
public override void Toggle(KTreeNode node)
{
if (node == _selectedNode)
_selectedNode = null;
else
_selectedNode = node;
}
}
public class Multiple : KSelectionManager
{
// TODO: use some sort of ordered set?
private readonly List<KTreeNode> _selection = new List<KTreeNode>();
public override IReadOnlyCollection<KTreeNode> CurrentSelection
{
get
{
return _selection;
}
}
public override void Clear()
{
_selection.Clear();
}
public override void Add(KTreeNode node)
{
if (!_selection.Contains(node))
_selection.Add(node);
}
public override void Toggle(KTreeNode node)
{
if (!_selection.Contains(node))
_selection.Add(node);
else
_selection.Remove(node);
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -0,0 +1,242 @@
/// 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;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Acacia.Controls
{
public class KTreeSubNode
{
// TODO: rerender on set
public string Text { get; set; }
public Control Control { get; set; }
}
public class KTreeNode : KTreeSubNode
{
#region Children
private KTreeNodeLoader _childLoader;
public KTreeNodeLoader ChildLoader
{
get { return _childLoader; }
set
{
if (_childLoader != value)
{
_childLoader = value;
Owner?.OnNodeChildrenChanged(this);
}
}
}
public KTreeNodes Children
{
get { return _childLoader.Children; }
}
#endregion
#region Properties
public int? ImageIndex { get; set; }
public object Tag { get; set; }
#endregion
#region State
private CheckState _checkState;
internal CheckState CheckStateDirect { get { return _checkState; } set { _checkState = value; } }
public CheckState CheckState
{
get { return _checkState; }
set
{
if (!HasCheckBox)
{
_checkState = value;
return;
}
if (_checkState != value)
{
KTree owner = Owner;
if (owner != null)
{
owner.CheckManager.SetCheck(this, value);
owner.Rerender(this);
}
else _checkState = value;
OnCheckStateChanged();
}
}
}
public delegate void CheckStateChangedHandler(KTreeNode node);
public event CheckStateChangedHandler CheckStateChanged;
protected virtual void OnCheckStateChanged()
{
if (CheckStateChanged != null)
CheckStateChanged(this);
Owner?.OnCheckStateChanged(this);
}
public bool IsChecked
{
get { return CheckState == CheckState.Checked; }
set { CheckState = value ? CheckState.Checked : CheckState.Unchecked; }
}
private bool _hasCheckBox = true;
public bool HasCheckBox
{
get { return _hasCheckBox; }
set
{
if (_hasCheckBox != value)
{
_hasCheckBox = value;
Owner?.Rerender(this);
}
}
}
private bool _isExpanded;
public bool IsExpanded
{
get { return _isExpanded; }
set
{
if (_isExpanded != value)
{
_isExpanded = value;
if (!_isExpanded)
_childLoader.NodeClosed();
if (!_isExpanded || _childLoader.NodeExpanding())
Owner?.OnNodeExpandedChanged(this);
}
}
}
public bool ToggleExpanded()
{
IsExpanded = !_isExpanded;
return _isExpanded;
}
public bool IsSelected
{
get
{
return Owner.SelectedNodes.Contains(this);
}
}
private bool _isSelectable = true;
public bool IsSelectable
{
get { return _isSelectable; }
set { _isSelectable = value; } // TODO: update node
}
public bool IsVisible
{
get
{
for (KTreeNode current = Parent; current != null; current = current.Parent)
{
if (!current.IsExpanded)
return false;
}
return true;
}
}
internal KTreeNodes ParentNodes { get; set; }
public KTreeNode Parent
{
get
{
return ParentNodes?.Parent;
}
}
public IEnumerable<KTreeNode> Ancestors
{
get
{
KTreeNode current = Parent;
while (current != null)
{
yield return current;
current = current.Parent;
}
}
}
public KTree Owner
{
get
{
return ParentNodes?.Owner;
}
}
public int Depth
{
get
{
int depth = 0;
for (KTreeNode current = Parent; current != null; current = current.Parent)
{
++depth;
}
return depth;
}
}
internal KTreeNodeMeasurements EffectiveDimension;
#endregion
#region Creation
public KTreeNode(string text = "", object tag = null)
{
this.Text = text;
this.Tag = tag;
_childLoader = new KTreeNodeLoaderStatic(this);
}
#endregion
}
}

View File

@ -0,0 +1,217 @@
/// 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;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Acacia.Controls
{
public class KTreeNodeLoader
{
public readonly KTreeNodes Children;
public enum LoadingState
{
NotLoaded,
Loading,
Loaded,
Error
}
public LoadingState State
{
get;
protected set;
}
public bool ReloadOnCloseOpen { get; set; }
public bool NeedsExpander
{
get
{
switch (State)
{
case LoadingState.Loaded:
return Children.Count > 0;
default:
return true;
}
}
}
public KTreeNodeLoader(KTreeNode parent)
{
Children = new KTreeNodes(parent);
}
internal void NodeClosed()
{
if (ReloadOnCloseOpen)
State = LoadingState.NotLoaded;
}
internal bool NodeExpanding()
{
switch (State)
{
case LoadingState.NotLoaded:
case LoadingState.Error:
StartLoadChildren();
return false;
default:
return true;
}
}
private void StartLoadChildren()
{
// Set the loading placeholder
State = LoadingState.Loading;
UpdateChildren(null);
// Load children asynchronously
KTreeNode node = Children.Parent;
OnBeginLoading(node);
KUITask
.New(() =>
{
return DoLoadChildren(node);
})
// Continuation in UI thread
.OnSuccess(result =>
{
// Loaded successfully, render
KTreeNodes childrenTemp = new KTreeNodes(node);
DoRenderChildren(node, result, childrenTemp);
State = LoadingState.Loaded;
return childrenTemp;
}, true)
.OnError(error =>
{
// On error return an empty node list
State = LoadingState.Error;
return new KTreeNodes(node);
}, true)
.OnCompletion(children =>
{
// Update nodes (or plaaceholder) and notify we're done
UpdateChildren(children);
OnEndLoading(node);
}, true)
.Start();
}
private KTreeNode _placeholder;
private void UpdateChildren(KTreeNodes newChildren)
{
Children.Owner?.BeginUpdate();
try
{
Children.Clear();
_placeholder = CreatePlaceholder(State, newChildren);
if (_placeholder != null)
Children.Add(_placeholder);
if (newChildren != null)
foreach (KTreeNode child in newChildren)
Children.Add(child);
}
finally
{
Children.Owner?.EndUpdate();
}
}
protected virtual KTreeNode CreatePlaceholder(LoadingState state, KTreeNodes children)
{
string text = GetPlaceholderText(state, children);
if (string.IsNullOrEmpty(text))
return null;
KTreeNode node = new KTreeNode(text);
node.HasCheckBox = false;
node.IsSelectable = false;
return node;
}
public delegate string PlaceholderTextHandler(KTreeNode node, LoadingState state, KTreeNodes children);
public PlaceholderTextHandler PlaceholderText;
protected virtual string GetPlaceholderText(LoadingState state, KTreeNodes children)
{
if (PlaceholderText == null)
return null;
return PlaceholderText(Children.Parent, state, children);
}
public delegate object LoadHandler(KTreeNode node);
public delegate void RenderHandler(KTreeNode node, object loaded, KTreeNodes children);
public LoadHandler LoadChildren;
public RenderHandler RenderChildren;
virtual protected object DoLoadChildren(KTreeNode node)
{
return LoadChildren(node);
}
virtual protected void DoRenderChildren(KTreeNode node, object loaded, KTreeNodes children)
{
RenderChildren(node, loaded, children);
}
public delegate void LoadingHandler(KTreeNode node);
public event LoadingHandler BeginLoading;
public event LoadingHandler EndLoading;
protected virtual void OnBeginLoading(KTreeNode node)
{
if (BeginLoading != null)
BeginLoading(node);
}
protected virtual void OnEndLoading(KTreeNode node)
{
if (EndLoading != null)
EndLoading(node);
}
public void Reload()
{
if (State != LoadingState.Loading)
{
StartLoadChildren();
}
}
}
internal class KTreeNodeLoaderStatic : KTreeNodeLoader
{
public KTreeNodeLoaderStatic(KTreeNode owner)
:
base(owner)
{
State = LoadingState.Loaded;
}
}
}

View File

@ -0,0 +1,157 @@
/// 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.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Acacia.Controls
{
internal class KTreeNodeMeasurements
{
public enum Part
{
Expander,
CheckBox,
Image,
Text,
Control,
None
}
private readonly KTreeNode _node;
private Rectangle _nodeRect;
private readonly KTree _options;
private readonly Padding _paddingOveral;
private readonly Size[] _sizes = new Size[(int)Part.None];
private readonly Padding[] _paddingInternal = new Padding[(int)Part.None];
public KTreeNodeMeasurements(KTreeNode node, KTree options)
{
this._node = node;
this._options = options;
this._nodeRect = new Rectangle(_node.Depth * _options.NodeIndent, 0, 0, 0);
_paddingOveral = options.NodePadding;
}
private KTreeNodeMeasurements(KTreeNodeMeasurements orig, int x, int y)
{
this._node = orig._node;
this._options = orig._options;
this._paddingOveral = orig._paddingOveral;
this._sizes = (Size[])orig._sizes.Clone();
// The node rectangle is the sum of the widths, and the maximum height (plus padding).
// TODO: special handling for control part, make that fit with e.g. a Dock option?
_nodeRect = new Rectangle(orig._nodeRect.X + x, y + orig._nodeRect.Y,
_sizes.Select((i) => i.Width).Sum() + _paddingOveral.Horizontal,
_sizes.Select((i) => i.Height).Max() + _paddingOveral.Vertical);
for (int i = 0; i < (int)Part.None; ++i)
{
_paddingInternal[i] = new Padding();
// Align any parts whose height does not match the total height
if (_sizes[i].Height != InnerRect.Height)
{
_paddingInternal[i].Bottom = (InnerRect.Height - _sizes[i].Height) / 2;
_paddingInternal[i].Top = (InnerRect.Height - _sizes[i].Height) - _paddingInternal[i].Bottom;
// Quick hack to make sure checkboxes are properly aligned, make the rect square again
// TODO: use padding/dock modes for this
if (i == (int)Part.CheckBox && !_sizes[i].IsEmpty && _sizes[i].IsSquare())
{
_paddingInternal[i].Left = _paddingInternal[i].Bottom;
_paddingInternal[i].Right = _paddingInternal[i].Top;
}
}
}
}
public KTreeNodeMeasurements Offset(int x, int y)
{
return new KTreeNodeMeasurements(this, x, y);
}
public Size this[Part part]
{
get { return _sizes[(int)part]; }
set { _sizes[(int)part] = value; }
}
public Rectangle NodeRect
{
get
{
return _nodeRect;
}
}
private Rectangle InnerRect
{
get
{
return _nodeRect.Shrink(_paddingOveral);
}
}
/// <summary>
///
/// </summary>
/// <param name="part"></param>
/// <param name="inner">If true, returns the rectangle without padding. Otherwise padding is included.</param>
/// <returns></returns>
public Rectangle GetPartRect(Part part, bool inner)
{
Rectangle r = InnerRect;
for (Part i = (Part)0; i < part; ++i)
{
r.Offset(_sizes[(int)i].Width + _paddingInternal[(int)i].Horizontal, 0);
}
r.Width = _sizes[(int)part].Width + _paddingInternal[(int)part].Horizontal;
if (inner)
r = r.Shrink(_paddingInternal[(int)part]);
return r;
}
public Part? HitTest(int x)
{
// Check the parts
for (Part i = (Part)0; i < Part.None; ++i)
{
// TODO: this could be more efficient, but that requires duplicating the layout logic
if (GetPartRect(i, false).ContainsX(x))
return i;
}
return Part.None;
}
public override string ToString()
{
string s = string.Format("Node={0}, Inner={1}", NodeRect, InnerRect);
for(Part part = (Part)0; part < Part.None; ++part)
{
s += string.Format(", {0}={1} ({2}", part, GetPartRect(part, false), _sizes[(int)part]);
}
return s;
}
}
}

View File

@ -0,0 +1,105 @@
/// 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;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Acacia.Controls
{
public class KTreeNodes : ICollection<KTreeNode>
{
private readonly List<KTreeNode> _items = new List<KTreeNode>();
private readonly KTreeNode _parent;
private KTree _owner;
public KTreeNode Parent { get { return _parent; } }
public KTree Owner
{
get
{
// TODO: this could be cached, but that's tricky with removal of nodes
KTreeNodes current = this;
while (current != null && current._owner == null)
{
current = current._parent.ParentNodes;
}
return current?._owner;
}
}
internal KTreeNodes(KTreeNode parent)
{
this._parent = parent;
this._owner = null;
}
internal KTreeNodes(KTree owner)
{
this._parent = null;
this._owner = owner;
}
public int Count { get{return _items.Count;}}
public bool IsReadOnly { get { return ((ICollection<KTreeNode>)_items).IsReadOnly; } }
public void Add(KTreeNode item)
{
_items.Add(item);
item.ParentNodes = this;
Owner?.OnNodeAdded(_parent, item);
}
public void Clear()
{
Owner?.OnNodeCleared(_parent);
_items.Clear();
}
public bool Contains(KTreeNode item)
{
return _items.Contains(item);
}
public void CopyTo(KTreeNode[] array, int arrayIndex)
{
_items.CopyTo(array, arrayIndex);
}
public IEnumerator<KTreeNode> GetEnumerator()
{
return ((ICollection<KTreeNode>)_items).GetEnumerator();
}
public bool Remove(KTreeNode item)
{
if (!_items.Remove(item))
return false;
Owner?.OnNodeRemoved(_parent, item);
return true;
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((ICollection<KTreeNode>)_items).GetEnumerator();
}
}
}

View File

@ -0,0 +1,173 @@
/// 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 Acacia.Native;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;
namespace Acacia.Controls
{
internal abstract class KTreeRenderer
{
private Rectangle _clientRect;
private Rectangle _totalRect;
protected KTree _tree;
public Rectangle TotalRect { get { return _totalRect; } }
public void Init(Rectangle clientRect, KTree tree)
{
this._clientRect = clientRect;
this._tree = tree;
_totalRect = new Rectangle(0, 0, 0, 0);
}
#region Measuring
internal KTreeNodeMeasurements MeasureNode(Graphics graphics, KTreeNode node)
{
// Determine the row rectangle
KTreeNodeMeasurements dims = GetNodeSize(graphics, node).Offset(_totalRect.X, _totalRect.Height);
node.EffectiveDimension = dims;
// Set up for the next node
_totalRect.Height += dims.NodeRect.Height;
_totalRect.Width = Math.Max(_totalRect.Right, dims.NodeRect.Right) - _totalRect.X;
return dims;
}
protected KTreeNodeMeasurements GetNodeSize(Graphics graphics, KTreeNode node)
{
KTreeNodeMeasurements dimension = new KTreeNodeMeasurements(node, _tree);
// Expander
dimension[KTreeNodeMeasurements.Part.Expander] = GetExpanderSize(graphics, node);
// Checkbox
if (node.Owner.CheckManager != null && node.HasCheckBox)
{
dimension[KTreeNodeMeasurements.Part.CheckBox] = CheckBoxRenderer.GetGlyphSize(graphics, CheckBoxState.CheckedNormal);
}
// Image
if (_tree.Images != null)
{
// Image size specified by imagelist
// Scale depending on resolution
dimension[KTreeNodeMeasurements.Part.Image] = _tree.Images.ImageSize.ScaleDpi(graphics);
}
// Text size
dimension[KTreeNodeMeasurements.Part.Text] = TextRenderer.MeasureText(graphics, node.Text, _tree.Font);
// Control
if (node.Control != null)
{
dimension[KTreeNodeMeasurements.Part.Control] = node.Control.PreferredSize;
}
return dimension;
}
protected abstract Size GetExpanderSize(Graphics graphics, KTreeNode node);
#endregion
#region Rendering
/// <summary>
///
/// </summary>
/// <param name="graphics">The graphics to render into</param>
/// <param name="node">The node</param>
/// <param name="scrollOffset">The current scrollbar offset</param>
/// <param name="highlight">If not null, the part of the node that is highlighted. May be Part.None to indicate the row is
/// highlighted, but not a specific part</param>
public void RenderNode(Graphics graphics, KTreeNode node, Point scrollOffset, KTreeNodeMeasurements.Part? highlight)
{
// Make sure the node has been measured
if (node.EffectiveDimension == null)
MeasureNode(graphics, node);
KTreeNodeMeasurements dims = node.EffectiveDimension.Offset(-scrollOffset.X, -scrollOffset.Y);
Rectangle containerRect = dims.NodeRect;
containerRect.X = _clientRect.X;
containerRect.Width = Math.Max(_totalRect.Width, _clientRect.Width);
// Selection background
RenderNodeOutline(graphics, node, _tree.FullRowSelect ? containerRect : dims.NodeRect, highlight);
// Expander
if (node.ChildLoader.NeedsExpander)
{
RenderNodeExpander(graphics, node, dims.GetPartRect(KTreeNodeMeasurements.Part.Expander, true), highlight);
}
// Checkbox
if (_tree.CheckManager != null && node.HasCheckBox)
{
RenderCheckBox(graphics, node, dims.GetPartRect(KTreeNodeMeasurements.Part.CheckBox, true), highlight);
}
// Images
if (_tree.Images != null && node.ImageIndex.HasValue && node.ImageIndex >= 0 && node.ImageIndex < _tree.Images.Images.Count)
{
Rectangle imageRect = dims.GetPartRect(KTreeNodeMeasurements.Part.Image, true);
// TODO: if the rectangle is larger than the image, this probably leads to upscaling.
// if the imagelist stores high-res icons as 16x16, that throws away resolution.
// make a custom image list to handle this? That could also handle scaling automatically
Image image = _tree.Images.Images[node.ImageIndex.Value];
graphics.DrawImage(image, imageRect.X, imageRect.Y, imageRect.Width, imageRect.Height);
}
// Text
RenderNodeText(graphics, node, dims.GetPartRect(KTreeNodeMeasurements.Part.Text, true), highlight);
// Control
if (node.Control != null)
{
node.Control.Bounds = dims.GetPartRect(KTreeNodeMeasurements.Part.Control, true);
}
}
protected abstract void RenderNodeOutline(Graphics graphics, KTreeNode node, Rectangle rect, KTreeNodeMeasurements.Part? highlight);
internal protected abstract void RenderNodeExpander(Graphics graphics, KTreeNode node, Rectangle rect, KTreeNodeMeasurements.Part? highlight);
protected virtual void RenderCheckBox(Graphics graphics, KTreeNode node, Rectangle rect, KTreeNodeMeasurements.Part? highlight)
{
int state = (int)node.CheckState * 4 + 1;
if (highlight != null && highlight.Value == KTreeNodeMeasurements.Part.CheckBox)
state += 1;
CheckBoxRenderer.DrawCheckBox(graphics, rect.Location, (CheckBoxState)state);
}
protected abstract void RenderNodeText(Graphics graphics, KTreeNode node, Rectangle rect, KTreeNodeMeasurements.Part? highlight);
public abstract void RenderControlBorder(Graphics graphics, Rectangle rect);
#endregion
}
}

View File

@ -0,0 +1,93 @@
/// 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.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;
namespace Acacia.Controls
{
internal class KTreeRendererDefault : KTreeRenderer
{
private readonly Size _expanderBoxSize = new Size(7, 7);
protected override Size GetExpanderSize(Graphics graphics, KTreeNode node)
{
return _expanderBoxSize;
}
internal protected override void RenderNodeExpander(Graphics graphics, KTreeNode node, Rectangle rect, KTreeNodeMeasurements.Part? highlight)
{
Color color = GetColor(node, highlight);
using (Pen pen = new Pen(color))
{
graphics.DrawRectangle(pen, rect.X - 1, rect.Y - 1, _expanderBoxSize.Width + 1, _expanderBoxSize.Height + 1);
int y = rect.Y + rect.Height / 2;
graphics.DrawLine(pen, rect.X + 1, y, rect.Right - 2, y);
if (!node.IsExpanded)
{
int x = rect.X + rect.Width / 2;
graphics.DrawLine(pen, x, rect.Y + 1, x, rect.Bottom - 2);
}
}
}
protected override void RenderNodeOutline(Graphics graphics, KTreeNode node, Rectangle rect, KTreeNodeMeasurements.Part? highlight)
{
if (highlight != null)
graphics.FillRectangle(SystemBrushes.FromSystemColor(SystemColors.HotTrack), rect);
else if (node.IsSelected)
graphics.FillRectangle(SystemBrushes.FromSystemColor(SystemColors.Highlight), rect);
if (_tree.ActiveNode == node && !node.IsSelected)
{
graphics.DrawRectangle(SystemPens.FromSystemColor(SystemColors.HotTrack), rect.X, rect.Y, rect.Width - 1, rect.Height - 1);
}
}
protected override void RenderNodeText(Graphics graphics, KTreeNode node, Rectangle rect, KTreeNodeMeasurements.Part? highlight)
{
TextRenderer.DrawText(graphics, node.Text, _tree.Font, rect, GetColor(node, highlight),
Color.Transparent, TextFormatFlags.Left | TextFormatFlags.VerticalCenter);
}
private Color GetColor(KTreeNode node, KTreeNodeMeasurements.Part? highlight)
{
Color color = _tree.ForeColor;
if (node.IsSelected)
color = SystemColors.HighlightText;
else if (highlight != null)
color = Color.White;
return color;
}
public override void RenderControlBorder(Graphics graphics, Rectangle rect)
{
using (Pen pen = new Pen(_tree.Enabled ? Color.Black : SystemColors.GrayText))
{
graphics.DrawRectangle(pen, new Rectangle(rect.X, rect.Y, rect.Width - 1, rect.Height - 1));
}
}
}
}

View File

@ -0,0 +1,159 @@
/// 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 Acacia.Native;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;
namespace Acacia.Controls
{
internal class KTreeRendererVisualStyles : KTreeRenderer
{
// From vsstyle.h
// enum TREEVIEWPARTS
// {
// TVP_TREEITEM = 1,
// TVP_GLYPH = 2,
// TVP_BRANCH = 3,
// TVP_HOTGLYPH = 4,
// };
// enum TREEITEMSTATES
// {
// TREIS_NORMAL = 1,
// TREIS_HOT = 2,
// TREIS_SELECTED = 3,
// TREIS_DISABLED = 4,
// TREIS_SELECTEDNOTFOCUS = 5,
// TREIS_HOTSELECTED = 6,
// };
//
// enum GLYPHSTATES
// {
// GLPS_CLOSED = 1,
// GLPS_OPENED = 2,
// };
private const string TREEVIEW = "Explorer::TreeView";
private readonly VisualStyleRenderer _treeViewItemNormal = new VisualStyleRenderer(TREEVIEW, 1, 1);
private readonly VisualStyleRenderer _treeViewItemHot = new VisualStyleRenderer(TREEVIEW, 1, 2);
private readonly VisualStyleRenderer _treeViewItemSelected = new VisualStyleRenderer(TREEVIEW, 1, 3);
private readonly VisualStyleRenderer _treeViewItemDisabled = new VisualStyleRenderer(TREEVIEW, 1, 4);
private readonly VisualStyleRenderer _treeViewItemSelectedNotFocus = new VisualStyleRenderer(TREEVIEW, 1, 5);
private readonly VisualStyleRenderer _treeViewItemHotSelected = new VisualStyleRenderer(TREEVIEW, 1, 6);
private readonly VisualStyleRenderer _treeViewGlyphClosed = new VisualStyleRenderer(TREEVIEW, 2, 1);
private readonly VisualStyleRenderer _treeViewGlyphOpened = new VisualStyleRenderer(TREEVIEW, 2, 2);
private readonly VisualStyleRenderer _treeViewGlyphHotClosed = new VisualStyleRenderer(TREEVIEW, 4, 1);
private readonly VisualStyleRenderer _treeViewGlyphHotOpened = new VisualStyleRenderer(TREEVIEW, 4, 2);
private Size? _glyphSize;
protected override Size GetExpanderSize(Graphics graphics, KTreeNode node)
{
// Get glyph size if needed
if (!_glyphSize.HasValue)
_glyphSize = _treeViewGlyphOpened.GetPartSize(graphics, ThemeSizeType.True);
return _glyphSize.Value;
}
internal protected override void RenderNodeExpander(Graphics graphics, KTreeNode node, Rectangle rect, KTreeNodeMeasurements.Part? highlight)
{
if (highlight != null && highlight.Value == KTreeNodeMeasurements.Part.Expander)
{
if (node.IsExpanded)
_treeViewGlyphHotOpened.DrawBackground(graphics, rect);
else
_treeViewGlyphHotClosed.DrawBackground(graphics, rect);
}
else
{
if (node.IsExpanded)
_treeViewGlyphOpened.DrawBackground(graphics, rect);
else
_treeViewGlyphClosed.DrawBackground(graphics, rect);
}
}
private VisualStyleRenderer GetStyle(KTreeNode node, KTreeNodeMeasurements.Part? highlight)
{
if (!_tree.Enabled)
{
return _treeViewItemDisabled;
}
else if (highlight != null)
{
if (node.IsSelected)
return _treeViewItemHotSelected;
else
return _treeViewItemHot;
}
else
{
if (node.IsSelected)
{
if (_tree.Focused)
return _treeViewItemSelected;
else
return _treeViewItemSelectedNotFocus;
}
else
return _treeViewItemNormal;
}
}
protected override void RenderNodeOutline(Graphics graphics, KTreeNode node, Rectangle rect, KTreeNodeMeasurements.Part? highlight)
{
// Draw one pixel too far, to overlap top and bottom borders for a continuous selection
Rectangle highlightRect = new Rectangle(rect.X, rect.Y, rect.Width, rect.Height + 1);
if (_tree.ActiveNode == node && _tree.Focused)
{
if (node.IsSelected)
_treeViewItemHotSelected.DrawBackground(graphics, highlightRect);
else
_treeViewItemNormal.DrawBackground(graphics, highlightRect);
}
else if (node.IsSelected || highlight != null)
{
GetStyle(node, highlight).DrawBackground(graphics, highlightRect);
}
}
protected override void RenderNodeText(Graphics graphics, KTreeNode node, Rectangle rect, KTreeNodeMeasurements.Part? highlight)
{
Color foreColor = GetStyle(node, highlight).GetColor(ColorProperty.TextColor);
TextRenderer.DrawText(graphics, node.Text, _tree.Font, rect, foreColor, Color.Transparent,
TextFormatFlags.Left | TextFormatFlags.VerticalCenter);
}
public override void RenderControlBorder(Graphics graphics, Rectangle rect)
{
Color color = (_tree.Enabled ? _treeViewItemNormal : _treeViewItemDisabled).GetColor(ColorProperty.BorderColor);
using (Pen pen = new Pen(_tree.Enabled ? Color.Black : SystemColors.GrayText))
{
graphics.DrawRectangle(pen, new Rectangle(rect.X, rect.Y, rect.Width - 1, rect.Height - 1));
}
}
}
}

View File

@ -0,0 +1,570 @@
/// 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.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Acacia.Controls
{
/// <summary>
/// UI progress indicator for tasks. All properties will be accessed in the UI thread.
/// </summary>
public interface KUITaskProgress
{
string BusyText { get; set; }
/// <summary>
/// Sets the busy state.
/// </summary>
bool Busy { get; set; }
/// <summary>
/// Shows successful completion
/// </summary>
void ShowCompletion(string text);
/// <summary>
/// May be set to a cancellation source to allow cancellation.
/// </summary>
CancellationTokenSource Cancellation { get; set; }
}
public interface KUITaskContext
{
CancellationToken CancellationToken { get; }
/// <summary>
/// Adds a number of counts to the busy indicator. Can be invoked from any thread.
/// </summary>
/// <param name="count">The number of busy counts to add or subtract</param>
void AddBusy(int count);
/// <summary>
/// Sets the busy text. Can be invoked from any thread.
/// </summary>
/// <param name="text">The text</param>
void SetBusyText(string text);
}
public class KUITaskBase
{
#region Execution state
internal class ExecutionConfig : KUITaskContext
{
public readonly KUITaskBase Root;
internal readonly ConcurrentDictionary<KUITaskBase,bool> Tasks = new ConcurrentDictionary<KUITaskBase, bool>();
internal readonly TaskScheduler UIContext;
internal readonly CancellationTokenSource _cancel;
public ExecutionConfig(KUITaskBase root)
{
this.Root = root;
Tasks.TryAdd(Root, false);
// Determine the UI context, creating a new one if required
if (SynchronizationContext.Current == null)
SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());
UIContext = TaskScheduler.FromCurrentSynchronizationContext();
// Create a cancellation source
_cancel = new CancellationTokenSource();
}
public CancellationToken CancellationToken { get { return _cancel.Token; } }
#region Busy indication
public KUITaskProgress Progress { get; set; }
private int _busyCount;
public void AddBusy(int count)
{
if (count == 0)
return;
bool oldBusy = _busyCount != 0;
_busyCount += count;
bool busy = _busyCount != 0;
if (oldBusy != busy)
{
// TODO: will the synchronisation context always point to the windows forms one, or can someone mess with it?
SynchronizationContext.Current.Send((b) =>
{
bool isBusy = (bool)b;
Progress.Busy = isBusy;
if (!isBusy)
Progress.Cancellation = null;
}, busy);
}
}
public int BusyCount { get { return _busyCount; } }
public void SetBusyText(string text)
{
SynchronizationContext.Current.Send((t) =>
{
Progress.BusyText = (string)t;
}, text);
}
internal void TaskFinished(KUITaskBase task)
{
Tasks.TryUpdate(task, true, false);
}
#endregion
}
internal class ExecutionState
{
public readonly ExecutionConfig Config;
private readonly object _result;
private readonly Exception _exception;
internal ExecutionState(ExecutionConfig config, object result, Exception exception)
{
this.Config = config;
this._result = result;
this._exception = exception;
}
internal ExecutionState NewVoid()
{
return new ExecutionState(Config, null, null);
}
internal ExecutionState NewResult(object result)
{
return new ExecutionState(Config, result, null);
}
internal ExecutionState NewException(Exception e)
{
return new ExecutionState(Config, null, e);
}
internal bool HasException
{
get { return _exception != null; }
}
internal object GetResult(TaskExecutor.Options options)
{
if ((options & TaskExecutor.Options.ErrorOnly) != 0)
return _exception;
else
return _result;
}
}
#endregion
#region Executor
internal protected class TaskExecutor
{
[Flags]
public enum Options
{
None = 0,
UIContext = 1,
ErrorOnly = 2,
SuccessOnly = 4
}
private readonly Func<ExecutionState, ExecutionState> _action;
private readonly Options _options;
internal TaskExecutor(Func<ExecutionState, ExecutionState> action, Options options)
{
this._action = action;
this._options = options;
}
internal static Options OptionHelper(bool errorOnly, bool successOnly, bool inUI)
{
Options options = Options.None;
if (errorOnly)
options |= Options.ErrorOnly;
if (successOnly)
options |= Options.SuccessOnly;
if (inUI)
options |= Options.UIContext;
return options;
}
internal Task<ExecutionState> Execute(KUITaskBase task, ExecutionState state)
{
Func<ExecutionState> action = () =>
{
ExecutionState result = state;
// TODO: do this outside the task. However, that requires returning some kind of task
bool execute = true;
if ((_options & Options.ErrorOnly) != 0 && !state.HasException)
execute = false;
else if ((_options & Options.SuccessOnly) != 0 && state.HasException)
execute = false;
// Always clean up one busy count when the task finishes
int busyCountDiff = -1;
if (execute)
{
int busyCountBefore = state.Config.BusyCount;
try
{
result = _action(state);
}
catch (Exception e)
{
result = state.NewException(e);
// If there is an exception, restore the busy count
busyCountDiff -= state.Config.BusyCount - busyCountBefore;
}
}
state.Config.TaskFinished(task);
state.Config.AddBusy(busyCountDiff);
return result;
};
return Task.Factory.StartNew(action, state.Config.CancellationToken, TaskCreationOptions.None, GetContext(state));
}
private TaskScheduler GetContext(ExecutionState state)
{
return (_options & Options.UIContext) != 0 ? state.Config.UIContext : TaskScheduler.Default;
}
#region Creators
// Passes nothing
public static TaskExecutor Void(Action action, Options options)
{
return new TaskExecutor((s) =>
{
action();
return s.NewVoid();
}, options);
}
public static TaskExecutor Void<ResultType>(Func<ResultType> action, Options options)
{
return new TaskExecutor((s) =>
{
ResultType result = action();
return s.NewResult(result);
}, options);
}
// Passes the a parameter
public static TaskExecutor Param<ParamType, ResultType>(Func<ParamType, ResultType> action, Options options)
{
return new TaskExecutor((s) =>
{
ResultType result = action((ParamType)s.GetResult(options));
return s.NewResult(result);
}, options);
}
public static TaskExecutor Param<ParamType>(Action<ParamType> action, Options options)
{
return new TaskExecutor((s) =>
{
action((ParamType)s.GetResult(options));
return s.NewVoid();
}, options);
}
// Passes the task context
public static TaskExecutor TaskContext<ResultType>(Func<KUITaskContext, ResultType> action, Options options)
{
return new TaskExecutor((s) =>
{
ResultType result = action(s.Config);
return s.NewResult(result);
}, options);
}
public static TaskExecutor TaskContext(Action<KUITaskContext> action, Options options)
{
return new TaskExecutor((s) =>
{
action(s.Config);
return s.NewVoid();
}, options);
}
// Passes the task context and a parameter
public static TaskExecutor TaskContextParam<ParamType, ResultType>(Func<KUITaskContext, ParamType, ResultType> action, Options options)
{
return new TaskExecutor((s) =>
{
ResultType result = action(s.Config, (ParamType)s.GetResult(options));
return s.NewResult(result);
}, options);
}
public static TaskExecutor TaskContextParam<ParamType>(Action<KUITaskContext, ParamType> action, Options options)
{
return new TaskExecutor((s) =>
{
action(s.Config, (ParamType)s.GetResult(options));
return s.NewVoid();
}, options);
}
#endregion
}
#endregion
protected readonly TaskExecutor _executor;
private ExecutionConfig _config;
private readonly Mutex _mutexTask = new Mutex();
private Task<ExecutionState> _task;
private readonly List<KUITaskBase> _next = new List<KUITaskBase>();
protected KUITaskBase(TaskExecutor exec, bool isRoot)
{
this._executor = exec;
this._config = isRoot ? new ExecutionConfig(this) : null;
}
public void Start(KUITaskProgress progress = null)
{
_config.Progress = progress ?? new DummyTaskProgress();
_config.Progress.Cancellation = _config._cancel;
_config.AddBusy(_config.Tasks.Count);
// Execute the root
_config.Root.DoStart(new ExecutionState(_config, null, null));
}
private void DoStart(ExecutionState state)
{
// Make sure we're not already started
if (_task != null)
throw new InvalidOperationException("Task chain already started");
// Start the task
_mutexTask.WaitOne();
try
{
_task = _executor.Execute(this, state);
// TODO: this could probably be outside the mutex
foreach (KUITaskBase next in _next)
AddContinuation(next);
}
finally
{
_mutexTask.ReleaseMutex();
}
}
protected TaskType Chain<TaskType>(TaskType next)
where TaskType : KUITaskBase
{
next._config = _config;
_config.Tasks.TryAdd(next, false);
_mutexTask.WaitOne();
try
{
// If the task is already started (or finished), add a chainer to that
// Otherwise, add it to the list
if (_task == null)
{
this._next.Add(next);
}
else
{
AddContinuation(next);
}
}
finally
{
_mutexTask.ReleaseMutex();
}
return next;
}
private void AddContinuation(KUITaskBase next)
{
// Start a synchronous task, the KUITask will detach if needed
_task.ContinueWith((prevTask) =>
{
next.DoStart(prevTask.Result);
}, _config.CancellationToken, TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.NotOnCanceled,
TaskScheduler.Current);
}
}
internal class DummyTaskProgress : KUITaskProgress
{
public bool Busy
{
get;
set;
}
public string BusyText
{
get;
set;
}
public CancellationTokenSource Cancellation
{
get;
set;
}
public void ShowCompletion(string text)
{
}
}
/// <summary>
/// Wrapper class for a chain of tasks with UI feedback
/// </summary>
/// <typeparam name="ResultType"></typeparam>
public class KUITask<ResultType> : KUITaskBase
{
internal protected KUITask(TaskExecutor exec, bool isRoot) : base(exec, isRoot)
{
}
#region Chainers
/// <summary>
/// Invoked on either success or error.
/// </summary>
public KUITask OnCompletion(Action<ResultType> func, bool inUI = false)
{
return Chain(new KUITask(TaskExecutor.Param(func, TaskExecutor.OptionHelper(false, false, inUI)), false));
}
// OnSuccess - Can either return nothing or a new result type (which could of course happen to be
// the current.
// Can accept Context and the current task result
public KUITask OnSuccess(Action func, bool inUI = false)
{
return Chain(new KUITask(TaskExecutor.Void(func, TaskExecutor.OptionHelper(false, true, inUI)), false));
}
public KUITask OnSuccess(Action<KUITaskContext> func, bool inUI = false)
{
return Chain(new KUITask(TaskExecutor.TaskContext(func, TaskExecutor.OptionHelper(false, true, inUI)), false));
}
public KUITask OnSuccess(Action<KUITaskContext, ResultType> func, bool inUI = false)
{
return Chain(new KUITask(TaskExecutor.TaskContextParam(func, TaskExecutor.OptionHelper(false, true, inUI)), false));
}
public KUITask<NewResultType> OnSuccess<NewResultType>(Func<ResultType, NewResultType> func, bool inUI = false)
{
return Chain(new KUITask<NewResultType>(TaskExecutor.Param(func, TaskExecutor.OptionHelper(false, true, inUI)), false));
}
// OnError - Can either return nothing, or the already expected return type. This allows an OnCompletion
// handler accepting either a result, or a dummy result returned by the error handler
// TODO: accept Context
public KUITask<ResultType> OnError(Func<Exception, ResultType> func, bool inUI = true)
{
return Chain(new KUITask<ResultType>(TaskExecutor.Param(func, TaskExecutor.OptionHelper(true, false, inUI)), false));
}
public KUITask OnError(Action<Exception> func, bool inUI = true)
{
return Chain(new KUITask(TaskExecutor.Param(func, TaskExecutor.OptionHelper(true, false, inUI)), false));
}
#endregion
}
public class KUITask : KUITaskBase
{
internal protected KUITask(TaskExecutor exec, bool isRoot) : base(exec, isRoot)
{
}
#region Chainers
public KUITask OnError(Action<Exception> func, bool inUI = true)
{
return Chain(new KUITask(TaskExecutor.Param(func, TaskExecutor.OptionHelper(true, false, inUI)), false));
}
public KUITask OnSuccess(Action func, bool inUI = false)
{
return Chain(new KUITask(TaskExecutor.Void(func, TaskExecutor.OptionHelper(false, true, inUI)), false));
}
public KUITask OnSuccess(Action<KUITaskContext> func, bool inUI = false)
{
return Chain(new KUITask(TaskExecutor.TaskContext(func, TaskExecutor.OptionHelper(false, true, inUI)), false));
}
#endregion
#region Factory methods
public static KUITask New(Action<KUITaskContext> action)
{
return new KUITask(TaskExecutor.TaskContext(action, TaskExecutor.Options.None), true);
}
public static KUITask New(Action action)
{
throw new NotImplementedException();
}
public static KUITask<ResultType> New<ResultType>(Func<KUITaskContext, ResultType> action)
{
return new KUITask<ResultType>(TaskExecutor.TaskContext(action, TaskExecutor.Options.None), true);
}
public static KUITask<ResultType> New<ResultType>(Func<ResultType> action)
{
return new KUITask<ResultType>(TaskExecutor.Void(action, TaskExecutor.Options.None), true);
}
#endregion
}
public interface KUITaskExecutor
{
/// <summary>
/// Executes a task
/// </summary>
/// <param name="busyText">The text to display while the task is busy</param>
/// <param name="action">The action</param>
/// <returns>A task for the action</returns>
KUITask<ResultType> Execute<ResultType>(string busyText, Func<CancellationToken, ResultType> action);
}
}

View File

@ -0,0 +1,80 @@
/// 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.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Acacia.Controls
{
internal static class KUIUtil
{
#region Geometry
public static Rectangle Center(this Rectangle _this, Size size)
{
int x = _this.X + (_this.Width - size.Width) / 2;
int y = _this.Y + (_this.Height - size.Height) / 2;
return new Rectangle(x, y, size.Width, size.Height);
}
public static Rectangle Expand(this Rectangle _this, Padding padding)
{
Rectangle r = _this;
r.X -= padding.Left;
r.Y -= padding.Top;
r.Width += padding.Horizontal;
r.Height += padding.Vertical;
return r;
}
public static Rectangle Shrink(this Rectangle _this, Padding padding)
{
Rectangle r = _this;
r.X += padding.Left;
r.Y += padding.Top;
r.Width -= padding.Horizontal;
r.Height -= padding.Vertical;
return r;
}
public static bool ContainsX(this Rectangle _this, int x)
{
return (x >= _this.X && x < _this.Right);
}
public static bool ContainsY(this Rectangle _this, int y)
{
return (y >= _this.Y && y < _this.Bottom);
}
public static bool IsSquare(this Size _this)
{
return _this.Width == _this.Height;
}
public static Size ScaleDpi(this Size _this, Graphics graphics)
{
return new Size((int)(_this.Width * graphics.DpiX / 96), (int)(_this.Height * graphics.DpiY / 96));
}
#endregion
}
}

View File

@ -0,0 +1,224 @@
/// 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 Acacia.Utils;
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Text;
namespace Acacia
{
public static class DebugOptions
{
abstract public class Option<ValueType>
{
public readonly string Token;
public Option(string token)
{
this.Token = token;
}
abstract public string GetToken(ValueType value);
abstract public ValueType GetValue(string value);
}
public class BoolOption : Option<bool>
{
public readonly bool Inverse;
public BoolOption(string token, bool inverse)
:
base(inverse ? "-" + token : (token.Length == 0 ? "+" : token))
{
this.Inverse = inverse;
}
public override string GetToken(bool value)
{
if (Inverse)
value = !value;
if (value)
return Token;
else
return null;
}
public override bool GetValue(string value)
{
bool enabled = value == Token;
if (Inverse)
enabled = !enabled;
return enabled;
}
}
public class EnumOption<EnumType> : Option<EnumType>
{
private EnumType DefaultValue
{
get
{
return (EnumType)typeof(EnumType).GetEnumValues().GetValue(0);
}
}
public EnumOption(string token)
:
base(token)
{
}
public override string GetToken(EnumType value)
{
if (value.Equals(DefaultValue))
return null;
return Token + "=" + value.ToString();
}
public override EnumType GetValue(string value)
{
if (string.IsNullOrEmpty(value))
return DefaultValue;
else
{
if (value.ToLower().StartsWith(Token.ToLower() + "="))
value = value.Substring(Token.Length + 1);
return (EnumType)Enum.Parse(typeof(EnumType), value, true);
}
}
}
public class TimeSpanOption : Option<TimeSpan>
{
private readonly TimeSpan _defaultValue;
public TimeSpanOption(string token, TimeSpan defaultValue)
:
base(token)
{
this._defaultValue = defaultValue;
}
public override string GetToken(TimeSpan value)
{
if (value.Equals(_defaultValue))
return null;
return Token + "=" + value.ToString();
}
public override TimeSpan GetValue(string value)
{
if (string.IsNullOrEmpty(value))
return _defaultValue;
else
{
if (value.ToLower().StartsWith(Token.ToLower() + "="))
value = value.Substring(Token.Length + 1);
return TimeSpan.Parse(value);
}
}
}
// General
public static readonly BoolOption ENABLED = new BoolOption("", true);
public static readonly BoolOption FEATURE_DISABLED_DEFAULT = new BoolOption("", false);
public static readonly BoolOption OUTLOOK_UI = new BoolOption("UI", true);
public static readonly BoolOption OUTLOOK_UI_RIBBON = new BoolOption("Ribbon", true);
public static readonly BoolOption OUTLOOK_UI_CONTEXT_MENU = new BoolOption("ContextMenu", true);
public static readonly BoolOption WATCHER_ENABLED = new BoolOption("Watcher", true);
/// <summary>
/// Allows all options to return defaults, for testing
/// </summary>
public static bool ReturnDefaults
{
get;
set;
}
/// <summary>
/// The threading model
/// </summary>
public enum Threading
{
MainThread,
Background,
Synchronous
}
#region Access methods
public static string GetOptions(string prefix)
{
if (ReturnDefaults)
return null;
return RegistryUtil.GetConfigValue<string>(prefix, null, null);
}
public static ValueType GetOption<ValueType>(string prefix, Option<ValueType> option)
{
// Parse the options
Dictionary<string, string> tokens = ParseTokens(prefix);
string value;
tokens.TryGetValue(option.Token.ToLower(), out value);
return option.GetValue(value);
}
private static Dictionary<string,string> ParseTokens(string prefix)
{
Dictionary<string, string> tokens = new Dictionary<string, string>();
string value = GetOptions(prefix);
if (!string.IsNullOrEmpty(value))
{
foreach (string token in value.Split(','))
{
if (!string.IsNullOrEmpty(token))
{
string[] keyVal = token.Split(new[] { '=' }, 2);
if (!string.IsNullOrEmpty(keyVal[0]))
{
tokens[keyVal[0].ToLower()] = token;
}
}
}
}
return tokens;
}
public static void SetOption<ValueType>(string prefix, Option<ValueType> option, ValueType value)
{
Dictionary<string, string> tokens = ParseTokens(prefix);
// Update the token
string token = option.GetToken(value);
if (token != null)
tokens[option.Token.ToLower()] = token;
else
tokens.Remove(option.Token.ToLower());
// Write to registry
string newValue = string.Join(",", tokens.Values);
RegistryUtil.SetConfigValue(prefix, null, newValue, RegistryValueKind.String);
}
#endregion
}
}

View File

@ -0,0 +1,192 @@
namespace Acacia.Features.DebugSupport
{
partial class AboutDialog
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(AboutDialog));
this._layout = new System.Windows.Forms.TableLayoutPanel();
this._layoutForm = new System.Windows.Forms.TableLayoutPanel();
this.labelDateValue = new Acacia.Controls.KCopyLabel();
this.labelRevisionValue = new Acacia.Controls.KCopyLabel();
this.icon = new System.Windows.Forms.PictureBox();
this.labelTitle = new System.Windows.Forms.Label();
this.labelVersionCaption = new Acacia.Controls.KCopyLabel();
this.labelRevisionCaption = new Acacia.Controls.KCopyLabel();
this.richTextBox1 = new System.Windows.Forms.RichTextBox();
this.labelDateCaption = new Acacia.Controls.KCopyLabel();
this.linkKopano = new System.Windows.Forms.LinkLabel();
this.labelVersionValue = new Acacia.Controls.KCopyLabel();
this._buttons = new Acacia.Controls.KDialogButtons();
this._layout.SuspendLayout();
this._layoutForm.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.icon)).BeginInit();
this.SuspendLayout();
//
// _layout
//
resources.ApplyResources(this._layout, "_layout");
this._layout.Controls.Add(this._layoutForm, 0, 0);
this._layout.Controls.Add(this._buttons, 0, 1);
this._layout.Name = "_layout";
//
// _layoutForm
//
resources.ApplyResources(this._layoutForm, "_layoutForm");
this._layoutForm.Controls.Add(this.labelDateValue, 1, 4);
this._layoutForm.Controls.Add(this.labelRevisionValue, 1, 3);
this._layoutForm.Controls.Add(this.icon, 0, 0);
this._layoutForm.Controls.Add(this.labelTitle, 1, 0);
this._layoutForm.Controls.Add(this.labelVersionCaption, 0, 2);
this._layoutForm.Controls.Add(this.labelRevisionCaption, 0, 3);
this._layoutForm.Controls.Add(this.richTextBox1, 0, 6);
this._layoutForm.Controls.Add(this.labelDateCaption, 0, 4);
this._layoutForm.Controls.Add(this.linkKopano, 1, 1);
this._layoutForm.Controls.Add(this.labelVersionValue, 1, 2);
this._layoutForm.Name = "_layoutForm";
//
// labelDateValue
//
this.labelDateValue.BorderStyle = System.Windows.Forms.BorderStyle.None;
resources.ApplyResources(this.labelDateValue, "labelDateValue");
this.labelDateValue.Name = "labelDateValue";
this.labelDateValue.ReadOnly = true;
this.labelDateValue.TabStop = false;
//
// labelRevisionValue
//
this.labelRevisionValue.BorderStyle = System.Windows.Forms.BorderStyle.None;
resources.ApplyResources(this.labelRevisionValue, "labelRevisionValue");
this.labelRevisionValue.Name = "labelRevisionValue";
this.labelRevisionValue.ReadOnly = true;
this.labelRevisionValue.TabStop = false;
//
// icon
//
resources.ApplyResources(this.icon, "icon");
this.icon.Name = "icon";
this.icon.TabStop = false;
//
// labelTitle
//
resources.ApplyResources(this.labelTitle, "labelTitle");
this.labelTitle.Name = "labelTitle";
//
// labelVersionCaption
//
this.labelVersionCaption.BorderStyle = System.Windows.Forms.BorderStyle.None;
resources.ApplyResources(this.labelVersionCaption, "labelVersionCaption");
this.labelVersionCaption.Name = "labelVersionCaption";
this.labelVersionCaption.ReadOnly = true;
this.labelVersionCaption.TabStop = false;
//
// labelRevisionCaption
//
this.labelRevisionCaption.BorderStyle = System.Windows.Forms.BorderStyle.None;
resources.ApplyResources(this.labelRevisionCaption, "labelRevisionCaption");
this.labelRevisionCaption.Name = "labelRevisionCaption";
this.labelRevisionCaption.ReadOnly = true;
this.labelRevisionCaption.TabStop = false;
//
// richTextBox1
//
this.richTextBox1.BorderStyle = System.Windows.Forms.BorderStyle.None;
this._layoutForm.SetColumnSpan(this.richTextBox1, 2);
resources.ApplyResources(this.richTextBox1, "richTextBox1");
this.richTextBox1.Name = "richTextBox1";
this.richTextBox1.ReadOnly = true;
this.richTextBox1.LinkClicked += new System.Windows.Forms.LinkClickedEventHandler(this.richTextBox1_LinkClicked);
//
// labelDateCaption
//
this.labelDateCaption.BorderStyle = System.Windows.Forms.BorderStyle.None;
resources.ApplyResources(this.labelDateCaption, "labelDateCaption");
this.labelDateCaption.Name = "labelDateCaption";
this.labelDateCaption.ReadOnly = true;
this.labelDateCaption.TabStop = false;
//
// linkKopano
//
resources.ApplyResources(this.linkKopano, "linkKopano");
this.linkKopano.Name = "linkKopano";
this.linkKopano.TabStop = true;
this.linkKopano.VisitedLinkColor = System.Drawing.Color.Blue;
this.linkKopano.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.linkKopano_LinkClicked);
//
// labelVersionValue
//
this.labelVersionValue.BorderStyle = System.Windows.Forms.BorderStyle.None;
resources.ApplyResources(this.labelVersionValue, "labelVersionValue");
this.labelVersionValue.Name = "labelVersionValue";
this.labelVersionValue.ReadOnly = true;
this.labelVersionValue.TabStop = false;
//
// _buttons
//
resources.ApplyResources(this._buttons, "_buttons");
this._buttons.ButtonSize = null;
this._buttons.Cancellation = null;
this._buttons.HasApply = false;
this._buttons.IsDirty = false;
this._buttons.Name = "_buttons";
//
// AboutDialog
//
resources.ApplyResources(this, "$this");
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this._layout);
this.MinimizeBox = false;
this.Name = "AboutDialog";
this.ShowInTaskbar = false;
this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Show;
this.TopMost = true;
this._layout.ResumeLayout(false);
this._layout.PerformLayout();
this._layoutForm.ResumeLayout(false);
this._layoutForm.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.icon)).EndInit();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.TableLayoutPanel _layout;
private System.Windows.Forms.TableLayoutPanel _layoutForm;
private Controls.KDialogButtons _buttons;
private System.Windows.Forms.PictureBox icon;
private System.Windows.Forms.Label labelTitle;
private Controls.KCopyLabel labelVersionCaption;
private Controls.KCopyLabel labelDateValue;
private Controls.KCopyLabel labelRevisionValue;
private Controls.KCopyLabel labelRevisionCaption;
private Controls.KCopyLabel labelDateCaption;
private System.Windows.Forms.LinkLabel linkKopano;
private System.Windows.Forms.RichTextBox richTextBox1;
private Controls.KCopyLabel labelVersionValue;
}
}

View File

@ -0,0 +1,59 @@
/// 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 Acacia.Utils;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Collections;
using Acacia.ZPush;
using System.Reflection;
using Acacia.UI;
using Acacia.Controls;
namespace Acacia.Features.DebugSupport
{
public partial class AboutDialog : KDialogNew
{
public AboutDialog()
{
InitializeComponent();
icon.Image = Properties.Resources.Kopano.ToBitmap();
labelVersionValue.Text = BuildVersions.VERSION;
labelRevisionValue.Text = BuildVersions.REVISION;
labelDateValue.Text = LibUtils.BuildTime.ToString();
}
private void linkKopano_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
System.Diagnostics.Process.Start(linkKopano.Text);
}
private void richTextBox1_LinkClicked(object sender, LinkClickedEventArgs e)
{
System.Diagnostics.Process.Start(e.LinkText);
}
}
}

View File

@ -0,0 +1,525 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="mscorlib" name="mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="_layout.ColumnCount" type="System.Int32, mscorlib">
<value>1</value>
</data>
<data name="_layoutForm.ColumnCount" type="System.Int32, mscorlib">
<value>2</value>
</data>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="labelDateValue.Dock" type="System.Windows.Forms.DockStyle, System.Windows.Forms">
<value>Fill</value>
</data>
<data name="labelDateValue.ImeMode" type="System.Windows.Forms.ImeMode, System.Windows.Forms">
<value>NoControl</value>
</data>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="labelDateValue.Location" type="System.Drawing.Point, System.Drawing">
<value>73, 132</value>
</data>
<data name="labelDateValue.Size" type="System.Drawing.Size, System.Drawing">
<value>355, 15</value>
</data>
<data name="labelDateValue.TabIndex" type="System.Int32, mscorlib">
<value>7</value>
</data>
<data name="&gt;&gt;labelDateValue.Name" xml:space="preserve">
<value>labelDateValue</value>
</data>
<data name="&gt;&gt;labelDateValue.Type" xml:space="preserve">
<value>Acacia.Controls.KCopyLabel, Kopano, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null</value>
</data>
<data name="&gt;&gt;labelDateValue.Parent" xml:space="preserve">
<value>_layoutForm</value>
</data>
<data name="&gt;&gt;labelDateValue.ZOrder" xml:space="preserve">
<value>0</value>
</data>
<data name="labelRevisionValue.Dock" type="System.Windows.Forms.DockStyle, System.Windows.Forms">
<value>Fill</value>
</data>
<data name="labelRevisionValue.ImeMode" type="System.Windows.Forms.ImeMode, System.Windows.Forms">
<value>NoControl</value>
</data>
<data name="labelRevisionValue.Location" type="System.Drawing.Point, System.Drawing">
<value>73, 111</value>
</data>
<data name="labelRevisionValue.Size" type="System.Drawing.Size, System.Drawing">
<value>355, 15</value>
</data>
<data name="labelRevisionValue.TabIndex" type="System.Int32, mscorlib">
<value>6</value>
</data>
<data name="&gt;&gt;labelRevisionValue.Name" xml:space="preserve">
<value>labelRevisionValue</value>
</data>
<data name="&gt;&gt;labelRevisionValue.Type" xml:space="preserve">
<value>Acacia.Controls.KCopyLabel, Kopano, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null</value>
</data>
<data name="&gt;&gt;labelRevisionValue.Parent" xml:space="preserve">
<value>_layoutForm</value>
</data>
<data name="&gt;&gt;labelRevisionValue.ZOrder" xml:space="preserve">
<value>1</value>
</data>
<data name="icon.Dock" type="System.Windows.Forms.DockStyle, System.Windows.Forms">
<value>Fill</value>
</data>
<data name="icon.Location" type="System.Drawing.Point, System.Drawing">
<value>3, 3</value>
</data>
<data name="icon.Size" type="System.Drawing.Size, System.Drawing">
<value>64, 64</value>
</data>
<data name="icon.SizeMode" type="System.Windows.Forms.PictureBoxSizeMode, System.Windows.Forms">
<value>AutoSize</value>
</data>
<data name="icon.TabIndex" type="System.Int32, mscorlib">
<value>0</value>
</data>
<data name="&gt;&gt;icon.Name" xml:space="preserve">
<value>icon</value>
</data>
<data name="&gt;&gt;icon.Type" xml:space="preserve">
<value>System.Windows.Forms.PictureBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;icon.Parent" xml:space="preserve">
<value>_layoutForm</value>
</data>
<data name="&gt;&gt;icon.ZOrder" xml:space="preserve">
<value>2</value>
</data>
<data name="labelTitle.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="labelTitle.Dock" type="System.Windows.Forms.DockStyle, System.Windows.Forms">
<value>Fill</value>
</data>
<data name="labelTitle.Font" type="System.Drawing.Font, System.Drawing">
<value>Microsoft Sans Serif, 16.2pt</value>
</data>
<data name="labelTitle.Location" type="System.Drawing.Point, System.Drawing">
<value>73, 0</value>
</data>
<data name="labelTitle.Size" type="System.Drawing.Size, System.Drawing">
<value>355, 70</value>
</data>
<data name="labelTitle.TabIndex" type="System.Int32, mscorlib">
<value>1</value>
</data>
<data name="labelTitle.Text" xml:space="preserve">
<value>Kopano OL Extension</value>
</data>
<data name="labelTitle.TextAlign" type="System.Drawing.ContentAlignment, System.Drawing">
<value>MiddleLeft</value>
</data>
<data name="&gt;&gt;labelTitle.Name" xml:space="preserve">
<value>labelTitle</value>
</data>
<data name="&gt;&gt;labelTitle.Type" xml:space="preserve">
<value>System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;labelTitle.Parent" xml:space="preserve">
<value>_layoutForm</value>
</data>
<data name="&gt;&gt;labelTitle.ZOrder" xml:space="preserve">
<value>3</value>
</data>
<data name="labelVersionCaption.Location" type="System.Drawing.Point, System.Drawing">
<value>3, 90</value>
</data>
<data name="labelVersionCaption.Size" type="System.Drawing.Size, System.Drawing">
<value>56, 15</value>
</data>
<data name="labelVersionCaption.TabIndex" type="System.Int32, mscorlib">
<value>1</value>
</data>
<data name="labelVersionCaption.Text" xml:space="preserve">
<value>Version</value>
</data>
<data name="&gt;&gt;labelVersionCaption.Name" xml:space="preserve">
<value>labelVersionCaption</value>
</data>
<data name="&gt;&gt;labelVersionCaption.Type" xml:space="preserve">
<value>Acacia.Controls.KCopyLabel, Kopano, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null</value>
</data>
<data name="&gt;&gt;labelVersionCaption.Parent" xml:space="preserve">
<value>_layoutForm</value>
</data>
<data name="&gt;&gt;labelVersionCaption.ZOrder" xml:space="preserve">
<value>4</value>
</data>
<data name="labelRevisionCaption.ImeMode" type="System.Windows.Forms.ImeMode, System.Windows.Forms">
<value>NoControl</value>
</data>
<data name="labelRevisionCaption.Location" type="System.Drawing.Point, System.Drawing">
<value>3, 111</value>
</data>
<data name="labelRevisionCaption.Size" type="System.Drawing.Size, System.Drawing">
<value>62, 15</value>
</data>
<data name="labelRevisionCaption.TabIndex" type="System.Int32, mscorlib">
<value>4</value>
</data>
<data name="labelRevisionCaption.Text" xml:space="preserve">
<value>Revision</value>
</data>
<data name="&gt;&gt;labelRevisionCaption.Name" xml:space="preserve">
<value>labelRevisionCaption</value>
</data>
<data name="&gt;&gt;labelRevisionCaption.Type" xml:space="preserve">
<value>Acacia.Controls.KCopyLabel, Kopano, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null</value>
</data>
<data name="&gt;&gt;labelRevisionCaption.Parent" xml:space="preserve">
<value>_layoutForm</value>
</data>
<data name="&gt;&gt;labelRevisionCaption.ZOrder" xml:space="preserve">
<value>5</value>
</data>
<data name="richTextBox1.Dock" type="System.Windows.Forms.DockStyle, System.Windows.Forms">
<value>Fill</value>
</data>
<data name="richTextBox1.Location" type="System.Drawing.Point, System.Drawing">
<value>3, 173</value>
</data>
<data name="richTextBox1.Size" type="System.Drawing.Size, System.Drawing">
<value>425, 272</value>
</data>
<data name="richTextBox1.TabIndex" type="System.Int32, mscorlib">
<value>9</value>
</data>
<data name="richTextBox1.Text" xml:space="preserve">
<value>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 &lt;http://www.gnu.org/licenses/&gt;</value>
</data>
<data name="&gt;&gt;richTextBox1.Name" xml:space="preserve">
<value>richTextBox1</value>
</data>
<data name="&gt;&gt;richTextBox1.Type" xml:space="preserve">
<value>System.Windows.Forms.RichTextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;richTextBox1.Parent" xml:space="preserve">
<value>_layoutForm</value>
</data>
<data name="&gt;&gt;richTextBox1.ZOrder" xml:space="preserve">
<value>6</value>
</data>
<data name="labelDateCaption.Location" type="System.Drawing.Point, System.Drawing">
<value>3, 132</value>
</data>
<data name="labelDateCaption.Size" type="System.Drawing.Size, System.Drawing">
<value>38, 15</value>
</data>
<data name="labelDateCaption.TabIndex" type="System.Int32, mscorlib">
<value>5</value>
</data>
<data name="labelDateCaption.Text" xml:space="preserve">
<value>Date</value>
</data>
<data name="&gt;&gt;labelDateCaption.Name" xml:space="preserve">
<value>labelDateCaption</value>
</data>
<data name="&gt;&gt;labelDateCaption.Type" xml:space="preserve">
<value>Acacia.Controls.KCopyLabel, Kopano, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null</value>
</data>
<data name="&gt;&gt;labelDateCaption.Parent" xml:space="preserve">
<value>_layoutForm</value>
</data>
<data name="&gt;&gt;labelDateCaption.ZOrder" xml:space="preserve">
<value>7</value>
</data>
<data name="linkKopano.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="linkKopano.Location" type="System.Drawing.Point, System.Drawing">
<value>73, 70</value>
</data>
<data name="linkKopano.Size" type="System.Drawing.Size, System.Drawing">
<value>132, 17</value>
</data>
<data name="linkKopano.TabIndex" type="System.Int32, mscorlib">
<value>8</value>
</data>
<data name="linkKopano.Text" xml:space="preserve">
<value>https://kopano.com/</value>
</data>
<data name="&gt;&gt;linkKopano.Name" xml:space="preserve">
<value>linkKopano</value>
</data>
<data name="&gt;&gt;linkKopano.Type" xml:space="preserve">
<value>System.Windows.Forms.LinkLabel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;linkKopano.Parent" xml:space="preserve">
<value>_layoutForm</value>
</data>
<data name="&gt;&gt;linkKopano.ZOrder" xml:space="preserve">
<value>8</value>
</data>
<data name="labelVersionValue.Dock" type="System.Windows.Forms.DockStyle, System.Windows.Forms">
<value>Fill</value>
</data>
<data name="labelVersionValue.Location" type="System.Drawing.Point, System.Drawing">
<value>73, 90</value>
</data>
<data name="labelVersionValue.Size" type="System.Drawing.Size, System.Drawing">
<value>355, 15</value>
</data>
<data name="labelVersionValue.TabIndex" type="System.Int32, mscorlib">
<value>10</value>
</data>
<data name="&gt;&gt;labelVersionValue.Name" xml:space="preserve">
<value>labelVersionValue</value>
</data>
<data name="&gt;&gt;labelVersionValue.Type" xml:space="preserve">
<value>Acacia.Controls.KCopyLabel, Kopano, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null</value>
</data>
<data name="&gt;&gt;labelVersionValue.Parent" xml:space="preserve">
<value>_layoutForm</value>
</data>
<data name="&gt;&gt;labelVersionValue.ZOrder" xml:space="preserve">
<value>9</value>
</data>
<data name="_layoutForm.Dock" type="System.Windows.Forms.DockStyle, System.Windows.Forms">
<value>Fill</value>
</data>
<data name="_layoutForm.Location" type="System.Drawing.Point, System.Drawing">
<value>3, 3</value>
</data>
<data name="_layoutForm.RowCount" type="System.Int32, mscorlib">
<value>7</value>
</data>
<data name="_layoutForm.Size" type="System.Drawing.Size, System.Drawing">
<value>431, 448</value>
</data>
<data name="_layoutForm.TabIndex" type="System.Int32, mscorlib">
<value>0</value>
</data>
<data name="&gt;&gt;_layoutForm.Name" xml:space="preserve">
<value>_layoutForm</value>
</data>
<data name="&gt;&gt;_layoutForm.Type" xml:space="preserve">
<value>System.Windows.Forms.TableLayoutPanel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;_layoutForm.Parent" xml:space="preserve">
<value>_layout</value>
</data>
<data name="&gt;&gt;_layoutForm.ZOrder" xml:space="preserve">
<value>0</value>
</data>
<data name="_layoutForm.LayoutSettings" type="System.Windows.Forms.TableLayoutSettings, System.Windows.Forms">
<value>&lt;?xml version="1.0" encoding="utf-16"?&gt;&lt;TableLayoutSettings&gt;&lt;Controls&gt;&lt;Control Name="labelDateValue" Row="4" RowSpan="1" Column="1" ColumnSpan="1" /&gt;&lt;Control Name="labelRevisionValue" Row="3" RowSpan="1" Column="1" ColumnSpan="1" /&gt;&lt;Control Name="icon" Row="0" RowSpan="1" Column="0" ColumnSpan="1" /&gt;&lt;Control Name="labelTitle" Row="0" RowSpan="1" Column="1" ColumnSpan="1" /&gt;&lt;Control Name="labelVersionCaption" Row="2" RowSpan="1" Column="0" ColumnSpan="1" /&gt;&lt;Control Name="labelRevisionCaption" Row="3" RowSpan="1" Column="0" ColumnSpan="1" /&gt;&lt;Control Name="richTextBox1" Row="6" RowSpan="1" Column="0" ColumnSpan="2" /&gt;&lt;Control Name="labelDateCaption" Row="4" RowSpan="1" Column="0" ColumnSpan="1" /&gt;&lt;Control Name="linkKopano" Row="1" RowSpan="1" Column="1" ColumnSpan="1" /&gt;&lt;Control Name="labelVersionValue" Row="2" RowSpan="1" Column="1" ColumnSpan="1" /&gt;&lt;/Controls&gt;&lt;Columns Styles="AutoSize,0,Percent,100" /&gt;&lt;Rows Styles="AutoSize,0,AutoSize,0,AutoSize,0,AutoSize,0,AutoSize,0,Absolute,20,Percent,100" /&gt;&lt;/TableLayoutSettings&gt;</value>
</data>
<data name="_buttons.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="_buttons.AutoSizeMode" type="System.Windows.Forms.AutoSizeMode, System.Windows.Forms">
<value>GrowAndShrink</value>
</data>
<data name="_buttons.Dock" type="System.Windows.Forms.DockStyle, System.Windows.Forms">
<value>Fill</value>
</data>
<data name="_buttons.Location" type="System.Drawing.Point, System.Drawing">
<value>3, 456</value>
</data>
<data name="_buttons.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>3, 2, 3, 2</value>
</data>
<data name="_buttons.Size" type="System.Drawing.Size, System.Drawing">
<value>431, 39</value>
</data>
<data name="_buttons.TabIndex" type="System.Int32, mscorlib">
<value>1</value>
</data>
<data name="&gt;&gt;_buttons.Name" xml:space="preserve">
<value>_buttons</value>
</data>
<data name="&gt;&gt;_buttons.Type" xml:space="preserve">
<value>Acacia.Controls.KDialogButtons, Kopano, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null</value>
</data>
<data name="&gt;&gt;_buttons.Parent" xml:space="preserve">
<value>_layout</value>
</data>
<data name="&gt;&gt;_buttons.ZOrder" xml:space="preserve">
<value>1</value>
</data>
<data name="_layout.Dock" type="System.Windows.Forms.DockStyle, System.Windows.Forms">
<value>Fill</value>
</data>
<data name="_layout.Location" type="System.Drawing.Point, System.Drawing">
<value>8, 7</value>
</data>
<data name="_layout.RowCount" type="System.Int32, mscorlib">
<value>2</value>
</data>
<data name="_layout.Size" type="System.Drawing.Size, System.Drawing">
<value>437, 497</value>
</data>
<data name="_layout.TabIndex" type="System.Int32, mscorlib">
<value>0</value>
</data>
<data name="&gt;&gt;_layout.Name" xml:space="preserve">
<value>_layout</value>
</data>
<data name="&gt;&gt;_layout.Type" xml:space="preserve">
<value>System.Windows.Forms.TableLayoutPanel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;_layout.Parent" xml:space="preserve">
<value>$this</value>
</data>
<data name="&gt;&gt;_layout.ZOrder" xml:space="preserve">
<value>0</value>
</data>
<data name="_layout.LayoutSettings" type="System.Windows.Forms.TableLayoutSettings, System.Windows.Forms">
<value>&lt;?xml version="1.0" encoding="utf-16"?&gt;&lt;TableLayoutSettings&gt;&lt;Controls&gt;&lt;Control Name="_layoutForm" Row="0" RowSpan="1" Column="0" ColumnSpan="1" /&gt;&lt;Control Name="_buttons" Row="1" RowSpan="1" Column="0" ColumnSpan="1" /&gt;&lt;/Controls&gt;&lt;Columns Styles="Percent,100" /&gt;&lt;Rows Styles="Percent,100,AutoSize,0" /&gt;&lt;/TableLayoutSettings&gt;</value>
</data>
<metadata name="$this.Localizable" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<data name="$this.AutoScaleDimensions" type="System.Drawing.SizeF, System.Drawing">
<value>8, 16</value>
</data>
<data name="$this.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="$this.ClientSize" type="System.Drawing.Size, System.Drawing">
<value>453, 511</value>
</data>
<data name="$this.Padding" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>8, 7, 8, 7</value>
</data>
<data name="$this.StartPosition" type="System.Windows.Forms.FormStartPosition, System.Windows.Forms">
<value>CenterParent</value>
</data>
<data name="$this.Text" xml:space="preserve">
<value>About Kopano OL Extenion</value>
</data>
<data name="&gt;&gt;$this.Name" xml:space="preserve">
<value>AboutDialog</value>
</data>
<data name="&gt;&gt;$this.Type" xml:space="preserve">
<value>Acacia.Controls.KDialogNew, Kopano, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null</value>
</data>
</root>

View File

@ -0,0 +1,126 @@
namespace Acacia.Features.DebugSupport
{
partial class DebugDialog
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(DebugDialog));
this.tableMain = new System.Windows.Forms.TableLayoutPanel();
this.flowButtons = new System.Windows.Forms.FlowLayoutPanel();
this.buttonGC = new System.Windows.Forms.Button();
this.buttonRefresh = new System.Windows.Forms.Button();
this.buttonClose = new System.Windows.Forms.Button();
this.buttonLog = new System.Windows.Forms.Button();
this.Properties = new System.Windows.Forms.PropertyGrid();
this.tableMain.SuspendLayout();
this.flowButtons.SuspendLayout();
this.SuspendLayout();
//
// tableMain
//
resources.ApplyResources(this.tableMain, "tableMain");
this.tableMain.Controls.Add(this.flowButtons, 0, 1);
this.tableMain.Controls.Add(this.Properties, 0, 0);
this.tableMain.Name = "tableMain";
//
// flowButtons
//
resources.ApplyResources(this.flowButtons, "flowButtons");
this.flowButtons.Controls.Add(this.buttonGC);
this.flowButtons.Controls.Add(this.buttonRefresh);
this.flowButtons.Controls.Add(this.buttonClose);
this.flowButtons.Controls.Add(this.buttonLog);
this.flowButtons.Name = "flowButtons";
//
// buttonGC
//
resources.ApplyResources(this.buttonGC, "buttonGC");
this.buttonGC.Name = "buttonGC";
this.buttonGC.UseVisualStyleBackColor = true;
this.buttonGC.Click += new System.EventHandler(this.buttonGC_Click);
//
// buttonRefresh
//
resources.ApplyResources(this.buttonRefresh, "buttonRefresh");
this.buttonRefresh.Name = "buttonRefresh";
this.buttonRefresh.UseVisualStyleBackColor = true;
this.buttonRefresh.Click += new System.EventHandler(this.buttonRefresh_Click);
//
// buttonClose
//
resources.ApplyResources(this.buttonClose, "buttonClose");
this.buttonClose.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.buttonClose.Name = "buttonClose";
this.buttonClose.UseVisualStyleBackColor = true;
this.buttonClose.Click += new System.EventHandler(this.buttonClose_Click);
//
// buttonLog
//
resources.ApplyResources(this.buttonLog, "buttonLog");
this.buttonLog.Name = "buttonLog";
this.buttonLog.UseVisualStyleBackColor = true;
this.buttonLog.Click += new System.EventHandler(this.buttonLog_Click);
//
// Properties
//
resources.ApplyResources(this.Properties, "Properties");
this.Properties.DisabledItemForeColor = System.Drawing.SystemColors.ControlText;
this.Properties.Name = "Properties";
this.Properties.PropertySort = System.Windows.Forms.PropertySort.Categorized;
this.Properties.ToolbarVisible = false;
//
// DebugDialog
//
resources.ApplyResources(this, "$this");
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.CancelButton = this.buttonClose;
this.Controls.Add(this.tableMain);
this.MinimizeBox = false;
this.Name = "DebugDialog";
this.ShowInTaskbar = false;
this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Show;
this.TopMost = true;
this.tableMain.ResumeLayout(false);
this.tableMain.PerformLayout();
this.flowButtons.ResumeLayout(false);
this.flowButtons.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.TableLayoutPanel tableMain;
private System.Windows.Forms.FlowLayoutPanel flowButtons;
private System.Windows.Forms.Button buttonGC;
private System.Windows.Forms.PropertyGrid Properties;
private System.Windows.Forms.Button buttonRefresh;
private System.Windows.Forms.Button buttonClose;
private System.Windows.Forms.Button buttonLog;
}
}

View File

@ -0,0 +1,117 @@
/// 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 Acacia.Utils;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Collections;
using Acacia.ZPush;
using System.Reflection;
using Acacia.UI;
namespace Acacia.Features.DebugSupport
{
public partial class DebugDialog : KopanoDialog
{
public DebugDialog()
{
InitializeComponent();
Properties.SelectedObject = new DebugInfo();
}
private void UpdateFields()
{
Properties.Refresh();
}
#region Logging
private const string INDENT = "+";
private void ToLog()
{
// Create a new property grid and expand it, to access all items
// This beats implementing the property logic to fetch all of item.
PropertyGrid grid = new PropertyGrid();
grid.SelectedObject = Properties.SelectedObject;
grid.ExpandAllGridItems();
object view = grid.GetType().GetField("gridView", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(grid);
// Log each category recursively
GridItemCollection items = (GridItemCollection)view.GetType().InvokeMember("GetAllGridEntries", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, view, null);
foreach(GridItem item in items)
{
if (item.GridItemType == GridItemType.Category)
{
LogItem(item, string.Empty);
}
}
}
private void LogItem(GridItem item, string indent)
{
if (item.GridItemType == GridItemType.Category)
Logger.Instance.Info(this, "{0}{1}", indent, item.Label.Trim());
else
Logger.Instance.Info(this, "{0}{1}={2}", indent, item.Label.Trim(), item.Value);
foreach(GridItem child in item.GridItems)
{
LogItem(child, indent + INDENT);
}
}
#endregion
#region Event handlers
private void buttonGC_Click(object sender, EventArgs e)
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
UpdateFields();
}
private void buttonRefresh_Click(object sender, EventArgs e)
{
UpdateFields();
}
private void buttonClose_Click(object sender, EventArgs e)
{
this.Close();
}
private void buttonLog_Click(object sender, EventArgs e)
{
ToLog();
}
#endregion
}
}

View File

@ -0,0 +1,375 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="tableMain.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
<value>Top, Bottom, Left, Right</value>
</data>
<assembly alias="mscorlib" name="mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="tableMain.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="tableMain.AutoSizeMode" type="System.Windows.Forms.AutoSizeMode, System.Windows.Forms">
<value>GrowAndShrink</value>
</data>
<data name="tableMain.ColumnCount" type="System.Int32, mscorlib">
<value>1</value>
</data>
<data name="flowButtons.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
<value>Top, Bottom, Left, Right</value>
</data>
<data name="flowButtons.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="buttonGC.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="buttonGC.Location" type="System.Drawing.Point, System.Drawing">
<value>352, 4</value>
</data>
<data name="buttonGC.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>4, 4, 4, 4</value>
</data>
<data name="buttonGC.Size" type="System.Drawing.Size, System.Drawing">
<value>91, 33</value>
</data>
<data name="buttonGC.TabIndex" type="System.Int32, mscorlib">
<value>0</value>
</data>
<data name="buttonGC.Text" xml:space="preserve">
<value>Run GC</value>
</data>
<data name="&gt;&gt;buttonGC.Name" xml:space="preserve">
<value>buttonGC</value>
</data>
<data name="&gt;&gt;buttonGC.Type" xml:space="preserve">
<value>System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;buttonGC.Parent" xml:space="preserve">
<value>flowButtons</value>
</data>
<data name="&gt;&gt;buttonGC.ZOrder" xml:space="preserve">
<value>0</value>
</data>
<data name="buttonRefresh.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="buttonRefresh.Location" type="System.Drawing.Point, System.Drawing">
<value>253, 4</value>
</data>
<data name="buttonRefresh.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>4, 4, 4, 4</value>
</data>
<data name="buttonRefresh.Size" type="System.Drawing.Size, System.Drawing">
<value>91, 33</value>
</data>
<data name="buttonRefresh.TabIndex" type="System.Int32, mscorlib">
<value>1</value>
</data>
<data name="buttonRefresh.Text" xml:space="preserve">
<value>Refresh</value>
</data>
<data name="&gt;&gt;buttonRefresh.Name" xml:space="preserve">
<value>buttonRefresh</value>
</data>
<data name="&gt;&gt;buttonRefresh.Type" xml:space="preserve">
<value>System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;buttonRefresh.Parent" xml:space="preserve">
<value>flowButtons</value>
</data>
<data name="&gt;&gt;buttonRefresh.ZOrder" xml:space="preserve">
<value>1</value>
</data>
<data name="buttonClose.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="buttonClose.Location" type="System.Drawing.Point, System.Drawing">
<value>145, 4</value>
</data>
<data name="buttonClose.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>4, 4, 4, 4</value>
</data>
<data name="buttonClose.Size" type="System.Drawing.Size, System.Drawing">
<value>100, 33</value>
</data>
<data name="buttonClose.TabIndex" type="System.Int32, mscorlib">
<value>2</value>
</data>
<data name="buttonClose.Text" xml:space="preserve">
<value>Close</value>
</data>
<data name="&gt;&gt;buttonClose.Name" xml:space="preserve">
<value>buttonClose</value>
</data>
<data name="&gt;&gt;buttonClose.Type" xml:space="preserve">
<value>System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;buttonClose.Parent" xml:space="preserve">
<value>flowButtons</value>
</data>
<data name="&gt;&gt;buttonClose.ZOrder" xml:space="preserve">
<value>2</value>
</data>
<data name="buttonLog.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="buttonLog.Location" type="System.Drawing.Point, System.Drawing">
<value>62, 4</value>
</data>
<data name="buttonLog.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>4, 4, 4, 4</value>
</data>
<data name="buttonLog.Size" type="System.Drawing.Size, System.Drawing">
<value>75, 33</value>
</data>
<data name="buttonLog.TabIndex" type="System.Int32, mscorlib">
<value>3</value>
</data>
<data name="buttonLog.Text" xml:space="preserve">
<value>Log</value>
</data>
<data name="&gt;&gt;buttonLog.Name" xml:space="preserve">
<value>buttonLog</value>
</data>
<data name="&gt;&gt;buttonLog.Type" xml:space="preserve">
<value>System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;buttonLog.Parent" xml:space="preserve">
<value>flowButtons</value>
</data>
<data name="&gt;&gt;buttonLog.ZOrder" xml:space="preserve">
<value>3</value>
</data>
<data name="flowButtons.FlowDirection" type="System.Windows.Forms.FlowDirection, System.Windows.Forms">
<value>RightToLeft</value>
</data>
<data name="flowButtons.Location" type="System.Drawing.Point, System.Drawing">
<value>3, 470</value>
</data>
<data name="flowButtons.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>3, 2, 3, 2</value>
</data>
<data name="flowButtons.Size" type="System.Drawing.Size, System.Drawing">
<value>447, 41</value>
</data>
<data name="flowButtons.TabIndex" type="System.Int32, mscorlib">
<value>1</value>
</data>
<data name="&gt;&gt;flowButtons.Name" xml:space="preserve">
<value>flowButtons</value>
</data>
<data name="&gt;&gt;flowButtons.Type" xml:space="preserve">
<value>System.Windows.Forms.FlowLayoutPanel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;flowButtons.Parent" xml:space="preserve">
<value>tableMain</value>
</data>
<data name="&gt;&gt;flowButtons.ZOrder" xml:space="preserve">
<value>0</value>
</data>
<data name="Properties.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
<value>Top, Bottom, Left, Right</value>
</data>
<data name="Properties.HelpVisible" type="System.Boolean, mscorlib">
<value>False</value>
</data>
<data name="Properties.Location" type="System.Drawing.Point, System.Drawing">
<value>3, 2</value>
</data>
<data name="Properties.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>3, 2, 3, 2</value>
</data>
<data name="Properties.Size" type="System.Drawing.Size, System.Drawing">
<value>447, 464</value>
</data>
<data name="Properties.TabIndex" type="System.Int32, mscorlib">
<value>2</value>
</data>
<data name="&gt;&gt;Properties.Name" xml:space="preserve">
<value>Properties</value>
</data>
<data name="&gt;&gt;Properties.Type" xml:space="preserve">
<value>System.Windows.Forms.PropertyGrid, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;Properties.Parent" xml:space="preserve">
<value>tableMain</value>
</data>
<data name="&gt;&gt;Properties.ZOrder" xml:space="preserve">
<value>1</value>
</data>
<data name="tableMain.Location" type="System.Drawing.Point, System.Drawing">
<value>0, 0</value>
</data>
<data name="tableMain.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>3, 2, 3, 2</value>
</data>
<data name="tableMain.RowCount" type="System.Int32, mscorlib">
<value>2</value>
</data>
<data name="tableMain.Size" type="System.Drawing.Size, System.Drawing">
<value>453, 513</value>
</data>
<data name="tableMain.TabIndex" type="System.Int32, mscorlib">
<value>0</value>
</data>
<data name="&gt;&gt;tableMain.Name" xml:space="preserve">
<value>tableMain</value>
</data>
<data name="&gt;&gt;tableMain.Type" xml:space="preserve">
<value>System.Windows.Forms.TableLayoutPanel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;tableMain.Parent" xml:space="preserve">
<value>$this</value>
</data>
<data name="&gt;&gt;tableMain.ZOrder" xml:space="preserve">
<value>0</value>
</data>
<data name="tableMain.LayoutSettings" type="System.Windows.Forms.TableLayoutSettings, System.Windows.Forms">
<value>&lt;?xml version="1.0" encoding="utf-16"?&gt;&lt;TableLayoutSettings&gt;&lt;Controls&gt;&lt;Control Name="flowButtons" Row="1" RowSpan="1" Column="0" ColumnSpan="1" /&gt;&lt;Control Name="Properties" Row="0" RowSpan="1" Column="0" ColumnSpan="1" /&gt;&lt;/Controls&gt;&lt;Columns Styles="Percent,100" /&gt;&lt;Rows Styles="Percent,100,AutoSize,0" /&gt;&lt;/TableLayoutSettings&gt;</value>
</data>
<metadata name="$this.Localizable" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<data name="$this.AutoScaleDimensions" type="System.Drawing.SizeF, System.Drawing">
<value>8, 16</value>
</data>
<data name="$this.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="$this.ClientSize" type="System.Drawing.Size, System.Drawing">
<value>453, 511</value>
</data>
<data name="$this.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>3, 2, 3, 2</value>
</data>
<data name="$this.StartPosition" type="System.Windows.Forms.FormStartPosition, System.Windows.Forms">
<value>CenterParent</value>
</data>
<data name="$this.Text" xml:space="preserve">
<value>Debug</value>
</data>
<data name="&gt;&gt;$this.Name" xml:space="preserve">
<value>DebugDialog</value>
</data>
<data name="&gt;&gt;$this.Type" xml:space="preserve">
<value>System.Windows.Forms.Form, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
</root>

View File

@ -0,0 +1,278 @@
/// 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 Acacia.Utils;
using Acacia.ZPush;
using Microsoft.Office.Core;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Acacia.Features.DebugSupport
{
public enum DebugCategory
{
Version,
Memory,
Wrappers,
Misc,
System,
Accounts,
Features,
AddIns
}
public class DebugCategoryAttribute : CategoryAttribute
{
// Add tabs; these are not printed, but are used for the sorting
public DebugCategoryAttribute(DebugCategory order)
:
base(order.ToString().PadLeft(typeof(DebugCategory).GetEnumNames().Length - (int)order + order.ToString().Length, '\t'))
{
}
}
public class DebugInfoConverter : ExpandableObjectConverter
{
private class CustomPropertyDescriptor<TProperty, TComponent> : PropertyDescriptor
{
private readonly TProperty value;
public CustomPropertyDescriptor(string propertyName, DebugCategory category, TProperty value)
: base(propertyName, new Attribute[] { new DebugCategoryAttribute(category) })
{
this.value = value;
}
public override bool CanResetValue(object component) { return false; }
public override Type ComponentType { get { return typeof(TComponent); } }
public override object GetValue(object component) { return value; }
public override bool IsReadOnly { get { return true; } }
public override Type PropertyType { get { return typeof(TProperty); } }
public override void ResetValue(object component) { SetValue(component, null); }
public override void SetValue(object component, object value) { }
public override bool ShouldSerializeValue(object component) { return false; }
}
public override bool GetPropertiesSupported(ITypeDescriptorContext context)
{
return true;
}
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
{
PropertyDescriptorCollection properties = new PropertyDescriptorCollection(base.GetProperties(context, value, attributes).Cast<PropertyDescriptor>().ToArray());
DebugInfo info = value as DebugInfo;
if (info != null)
{
// Add accounts
foreach (ZPushAccount account in ThisAddIn.Instance.Watcher.Accounts.GetAccounts())
{
PropertyDescriptor p = new CustomPropertyDescriptor<ZPushAccount, DebugInfo>(account.DisplayName, DebugCategory.Accounts, account);
properties.Add(p);
}
// Add Features
foreach (Feature feature in ThisAddIn.Instance.Features)
{
PropertyDescriptor p = new CustomPropertyDescriptor<Feature, DebugInfo>(feature.Name, DebugCategory.Features, feature);
properties.Add(p);
}
// Add Add-ins
foreach (COMAddIn addin in ThisAddIn.Instance.Application.COMAddIns)
{
PropertyDescriptor p = new CustomPropertyDescriptor<string, DebugInfo>(addin.ProgId, DebugCategory.AddIns, addin.Description);
properties.Add(p);
}
}
return properties;
}
}
[TypeConverter(typeof(DebugInfoConverter))]
class DebugInfo
{
#region Version
[DebugCategory(DebugCategory.Version)]
public string Version { get { return BuildVersions.VERSION; } }
[DebugCategory(DebugCategory.Version)]
public string Revision { get { return BuildVersions.REVISION; } }
[DebugCategory(DebugCategory.Version)]
public string BuildDate { get { return LibUtils.BuildTime.ToString(); } }
#endregion
#region Memory
[DebugCategory(DebugCategory.Memory)]
public string TotalMemory { get { return MemoryToString(GC.GetTotalMemory(false)); } }
#endregion
#region Wrappers
[DebugCategory(DebugCategory.Wrappers)]
public long ActiveWrappers { get { return Statistics.CreatedWrappers - Statistics.DeletedWrappers; } }
[DebugCategory(DebugCategory.Wrappers)]
public long CreatedWrappers { get { return Statistics.CreatedWrappers; } }
[DebugCategory(DebugCategory.Wrappers)]
public long DeletedWrappers { get { return Statistics.DeletedWrappers; } }
[DebugCategory(DebugCategory.Wrappers)]
public long DisposedWrappers { get { return Statistics.DisposedWrappers; } }
[DebugCategory(DebugCategory.Wrappers)]
public long UndisposedWrappers { get { return Statistics.DeletedWrappers - Statistics.DisposedWrappers; } }
#endregion
#region Misc
[DebugCategory(DebugCategory.Misc)]
public string StartupTime { get { return TimeToString(Statistics.StartupTime); } }
[DebugCategory(DebugCategory.Misc)]
public LogLevel LogLevel
{
get { return Logger.Instance.MinLevel; }
set
{
Logger.Instance.SetLevel(value);
}
}
[DebugCategory(DebugCategory.Misc)]
public string Threading
{
get { return Tasks.Executor.Name; }
}
[DebugCategory(DebugCategory.Misc)]
public bool ZPushSync
{
get { return ThisAddIn.Instance.Watcher.Sync.Enabled; }
}
[DebugCategory(DebugCategory.Misc)]
public TimeSpan ZPushSyncPeriod
{
get { return ThisAddIn.Instance.Watcher.Sync.Period; }
}
[DebugCategory(DebugCategory.Misc)]
public string Build
{
get
{
#if DEBUG
return "Debug";
#else
return "Release";
#endif
}
}
#endregion
#region System
[DebugCategory(DebugCategory.System)]
public string Locale
{
get
{
return CultureInfo.CurrentUICulture.DisplayName;
}
}
[DebugCategory(DebugCategory.System)]
public string WindowsVersion
{
get
{
return Environment.OSVersion.Version.ToString();
}
}
[DebugCategory(DebugCategory.System)]
public string Architecture
{
get
{
return Environment.Is64BitOperatingSystem ? "64 bit" : "32 bit";
}
}
#endregion
#region Outlook
[DebugCategory(DebugCategory.System)]
public string OutlookVersion
{
get
{
return ThisAddIn.Instance.Application.Version;
}
}
[DebugCategory(DebugCategory.System)]
public string OutlookArchitecture
{
get
{
return Environment.Is64BitProcess ? "64 bit" : "32 bit";
}
}
#endregion
#region Helpers
private string TimeToString(Stopwatch time)
{
return time.ElapsedMilliseconds.ToString("#### ms");
}
private static readonly string[] SizeSuffixes = { "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" };
private string MemoryToString(long value)
{
if (value < 0) { return "-" + MemoryToString(-value); }
int i = 0;
decimal dValue = (decimal)value;
while (Math.Round(dValue / 1024) >= 1)
{
dValue /= 1024;
i++;
}
return string.Format("{0:n1} {1}", dValue, SizeSuffixes[i]);
}
#endregion
}
}

View File

@ -0,0 +1,89 @@
namespace Acacia.Features.DebugSupport
{
partial class DebugSupportSettings
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(DebugSupportSettings));
this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel();
this.labelLogLevel = new System.Windows.Forms.Label();
this.comboLogLevel = new System.Windows.Forms.ComboBox();
this.buttonShowLog = new System.Windows.Forms.Button();
this.tableLayoutPanel1.SuspendLayout();
this.SuspendLayout();
//
// tableLayoutPanel1
//
resources.ApplyResources(this.tableLayoutPanel1, "tableLayoutPanel1");
this.tableLayoutPanel1.BackColor = System.Drawing.SystemColors.Window;
this.tableLayoutPanel1.Controls.Add(this.labelLogLevel, 0, 0);
this.tableLayoutPanel1.Controls.Add(this.comboLogLevel, 1, 0);
this.tableLayoutPanel1.Controls.Add(this.buttonShowLog, 0, 1);
this.tableLayoutPanel1.Name = "tableLayoutPanel1";
//
// labelLogLevel
//
resources.ApplyResources(this.labelLogLevel, "labelLogLevel");
this.labelLogLevel.Name = "labelLogLevel";
//
// comboLogLevel
//
resources.ApplyResources(this.comboLogLevel, "comboLogLevel");
this.comboLogLevel.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.comboLogLevel.FormattingEnabled = true;
this.comboLogLevel.Name = "comboLogLevel";
this.comboLogLevel.SelectedIndexChanged += new System.EventHandler(this.comboLogLevel_SelectedIndexChanged);
//
// buttonShowLog
//
resources.ApplyResources(this.buttonShowLog, "buttonShowLog");
this.tableLayoutPanel1.SetColumnSpan(this.buttonShowLog, 2);
this.buttonShowLog.Name = "buttonShowLog";
this.buttonShowLog.UseVisualStyleBackColor = true;
this.buttonShowLog.Click += new System.EventHandler(this.buttonShowLog_Click);
//
// DebugSupportSettings
//
resources.ApplyResources(this, "$this");
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.tableLayoutPanel1);
this.Name = "DebugSupportSettings";
this.tableLayoutPanel1.ResumeLayout(false);
this.tableLayoutPanel1.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;
private System.Windows.Forms.Label labelLogLevel;
private System.Windows.Forms.ComboBox comboLogLevel;
private System.Windows.Forms.Button buttonShowLog;
}
}

View File

@ -0,0 +1,68 @@
/// 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.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Acacia.UI;
namespace Acacia.Features.DebugSupport
{
public partial class DebugSupportSettings : FeatureSettings
{
private readonly FeatureDebugSupport _feature;
public override Feature Feature
{
get
{
return _feature;
}
}
public DebugSupportSettings(FeatureDebugSupport feature = null)
{
this._feature = feature;
InitializeComponent();
for (int i = 0; i < typeof(LogLevel).GetEnumNames().Length; ++i)
comboLogLevel.Items.Add((LogLevel)i);
comboLogLevel.SelectedItem = Logger.Instance.MinLevel;
}
private void buttonShowLog_Click(object sender, EventArgs e)
{
if (_feature != null)
_feature.ShowLog();
}
private void comboLogLevel_SelectedIndexChanged(object sender, EventArgs e)
{
Dirty = Logger.Instance.MinLevel != (LogLevel)comboLogLevel.SelectedItem;
}
public override void Apply()
{
Logger.Instance.SetLevel((LogLevel)comboLogLevel.SelectedItem);
}
}
}

View File

@ -0,0 +1,267 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="tableLayoutPanel1.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
<value>Top, Left, Right</value>
</data>
<assembly alias="mscorlib" name="mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="tableLayoutPanel1.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="tableLayoutPanel1.ColumnCount" type="System.Int32, mscorlib">
<value>2</value>
</data>
<data name="labelLogLevel.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="labelLogLevel.Dock" type="System.Windows.Forms.DockStyle, System.Windows.Forms">
<value>Fill</value>
</data>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="labelLogLevel.Location" type="System.Drawing.Point, System.Drawing">
<value>3, 0</value>
</data>
<data name="labelLogLevel.Size" type="System.Drawing.Size, System.Drawing">
<value>53, 27</value>
</data>
<data name="labelLogLevel.TabIndex" type="System.Int32, mscorlib">
<value>0</value>
</data>
<data name="labelLogLevel.Text" xml:space="preserve">
<value>Log level:</value>
</data>
<data name="labelLogLevel.TextAlign" type="System.Drawing.ContentAlignment, System.Drawing">
<value>MiddleLeft</value>
</data>
<data name="&gt;&gt;labelLogLevel.Name" xml:space="preserve">
<value>labelLogLevel</value>
</data>
<data name="&gt;&gt;labelLogLevel.Type" xml:space="preserve">
<value>System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;labelLogLevel.Parent" xml:space="preserve">
<value>tableLayoutPanel1</value>
</data>
<data name="&gt;&gt;labelLogLevel.ZOrder" xml:space="preserve">
<value>0</value>
</data>
<data name="comboLogLevel.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
<value>Top, Bottom, Left, Right</value>
</data>
<data name="comboLogLevel.Location" type="System.Drawing.Point, System.Drawing">
<value>62, 3</value>
</data>
<data name="comboLogLevel.Size" type="System.Drawing.Size, System.Drawing">
<value>281, 21</value>
</data>
<data name="comboLogLevel.TabIndex" type="System.Int32, mscorlib">
<value>1</value>
</data>
<data name="&gt;&gt;comboLogLevel.Name" xml:space="preserve">
<value>comboLogLevel</value>
</data>
<data name="&gt;&gt;comboLogLevel.Type" xml:space="preserve">
<value>System.Windows.Forms.ComboBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;comboLogLevel.Parent" xml:space="preserve">
<value>tableLayoutPanel1</value>
</data>
<data name="&gt;&gt;comboLogLevel.ZOrder" xml:space="preserve">
<value>1</value>
</data>
<data name="buttonShowLog.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="buttonShowLog.Location" type="System.Drawing.Point, System.Drawing">
<value>3, 35</value>
</data>
<data name="buttonShowLog.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>3, 8, 8, 3</value>
</data>
<data name="buttonShowLog.Padding" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>8, 0, 8, 0</value>
</data>
<data name="buttonShowLog.Size" type="System.Drawing.Size, System.Drawing">
<value>132, 23</value>
</data>
<data name="buttonShowLog.TabIndex" type="System.Int32, mscorlib">
<value>2</value>
</data>
<data name="buttonShowLog.Text" xml:space="preserve">
<value>Open log file location</value>
</data>
<data name="&gt;&gt;buttonShowLog.Name" xml:space="preserve">
<value>buttonShowLog</value>
</data>
<data name="&gt;&gt;buttonShowLog.Type" xml:space="preserve">
<value>System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;buttonShowLog.Parent" xml:space="preserve">
<value>tableLayoutPanel1</value>
</data>
<data name="&gt;&gt;buttonShowLog.ZOrder" xml:space="preserve">
<value>2</value>
</data>
<data name="tableLayoutPanel1.Location" type="System.Drawing.Point, System.Drawing">
<value>0, 0</value>
</data>
<data name="tableLayoutPanel1.RowCount" type="System.Int32, mscorlib">
<value>2</value>
</data>
<data name="tableLayoutPanel1.Size" type="System.Drawing.Size, System.Drawing">
<value>346, 63</value>
</data>
<data name="tableLayoutPanel1.TabIndex" type="System.Int32, mscorlib">
<value>0</value>
</data>
<data name="&gt;&gt;tableLayoutPanel1.Name" xml:space="preserve">
<value>tableLayoutPanel1</value>
</data>
<data name="&gt;&gt;tableLayoutPanel1.Type" xml:space="preserve">
<value>System.Windows.Forms.TableLayoutPanel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;tableLayoutPanel1.Parent" xml:space="preserve">
<value>$this</value>
</data>
<data name="&gt;&gt;tableLayoutPanel1.ZOrder" xml:space="preserve">
<value>0</value>
</data>
<data name="tableLayoutPanel1.LayoutSettings" type="System.Windows.Forms.TableLayoutSettings, System.Windows.Forms">
<value>&lt;?xml version="1.0" encoding="utf-16"?&gt;&lt;TableLayoutSettings&gt;&lt;Controls&gt;&lt;Control Name="labelLogLevel" Row="0" RowSpan="1" Column="0" ColumnSpan="1" /&gt;&lt;Control Name="comboLogLevel" Row="0" RowSpan="1" Column="1" ColumnSpan="1" /&gt;&lt;Control Name="buttonShowLog" Row="1" RowSpan="1" Column="0" ColumnSpan="2" /&gt;&lt;/Controls&gt;&lt;Columns Styles="AutoSize,0,Percent,100" /&gt;&lt;Rows Styles="AutoSize,0,AutoSize,0" /&gt;&lt;/TableLayoutSettings&gt;</value>
</data>
<metadata name="$this.Localizable" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<data name="$this.AutoScaleDimensions" type="System.Drawing.SizeF, System.Drawing">
<value>6, 13</value>
</data>
<data name="$this.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="$this.Size" type="System.Drawing.Size, System.Drawing">
<value>349, 66</value>
</data>
<data name="&gt;&gt;$this.Name" xml:space="preserve">
<value>DebugSupportSettings</value>
</data>
<data name="&gt;&gt;$this.Type" xml:space="preserve">
<value>Acacia.UI.FeatureSettings, ZPush, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null</value>
</data>
</root>

View File

@ -0,0 +1,122 @@
/// 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 Microsoft.Office.Interop.Outlook;
using System;
using System.Collections.Generic;
using Acacia.Features.ReplyFlags;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Acacia.Utils;
using System.Threading;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using Acacia.UI;
using Acacia.ZPush;
using Acacia.UI.Outlook;
namespace Acacia.Features.DebugSupport
{
[AcaciaOption("Contains features to enable support and debugging of the plugin.")]
public class FeatureDebugSupport : Feature, FeatureWithRibbon
{
public FeatureDebugSupport()
{
}
public override void Startup()
{
RegisterButton(this, "About", false, ShowAbout);
if (Dialog)
RegisterButton(this, "Debug", false, ShowDialog);
RegisterButton(this, "Settings", false, ShowSettings);
}
#region About dialog
public void ShowAbout()
{
new AboutDialog().ShowDialog();
}
#endregion
#region Debug options
private static readonly DebugOptions.BoolOption OPTION_DIALOG = new DebugOptions.BoolOption("Dialog", false);
[AcaciaOption("Enables the debug dialog")]
public bool Dialog
{
get { return GetOption(OPTION_DIALOG); }
set { SetOption(OPTION_DIALOG, value); }
}
#endregion
#region Settings
public void ShowSettings()
{
new SettingsDialog().ShowDialog();
}
public override FeatureSettings GetSettings()
{
return new DebugSupportSettings(this);
}
#endregion
#region Debug dialog
private void ShowDialog()
{
new DebugDialog().Show();
}
#endregion
#region Log
public void ShowLog()
{
if (Logger.Instance.Path != null)
{
// This is roughly equivalent to starting explorer with /select, but has
// the benefit of reusing windows if it's done multiple times
IntPtr pidl = ILCreateFromPathW(Logger.Instance.Path);
SHOpenFolderAndSelectItems(pidl, 0, IntPtr.Zero, 0);
ILFree(pidl);
}
}
[DllImport("shell32.dll", CharSet = CharSet.Unicode)]
private static extern IntPtr ILCreateFromPathW(string pszPath);
[DllImport("shell32.dll")]
private static extern int SHOpenFolderAndSelectItems(IntPtr pidlFolder, int cild, IntPtr apidl, int dwFlags);
[DllImport("shell32.dll")]
private static extern void ILFree(IntPtr pidl);
#endregion
}
}

View File

@ -0,0 +1,44 @@
/// 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.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Acacia.Features.DebugSupport
{
/// <summary>
/// Object converter for a feature in the debug dialog.
/// </summary>
public class FeatureObjectConverter : ExpandableObjectConverter
{
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (destinationType == typeof(string) && value is Feature)
return ((Feature)value).ToDebugString();
return base.ConvertTo(context, culture, value, destinationType);
}
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return false;
}
}
}

View File

@ -0,0 +1,33 @@
/// 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.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Acacia.Features.DebugSupport
{
public static class Statistics
{
public static long CreatedWrappers;
public static long DeletedWrappers;
public static long DisposedWrappers;
public static Stopwatch StartupTime = new Stopwatch();
}
}

View File

@ -0,0 +1,259 @@
/// 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 Microsoft.Office.Interop.Outlook;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Acacia.UI;
using Acacia.Utils;
using System.Windows.Forms;
using Acacia.ZPush;
using System.ComponentModel;
using Acacia.Features.DebugSupport;
using Microsoft.Win32;
using Acacia.UI.Outlook;
using Acacia.Stubs;
namespace Acacia.Features
{
/// <summary>
/// A feature represents a modular piece of functionality that can be enabled in the plugin.
/// As all hooks must be registered from the same callback (Startup in ThisAddIn), the
/// Feature class is used to allow modules to register their hooks.
/// </summary>
[TypeConverter(typeof(FeatureObjectConverter))]
abstract public class Feature : LogContext
{
public readonly string Name;
protected Feature()
{
this.Name = GetFeatureName(GetType());
}
[Browsable(false)]
public string DisplayName
{
get { return StringUtil.GetResourceString("Feature_" + Name); }
}
public virtual FeatureSettings GetSettings()
{
return null;
}
protected static Microsoft.Office.Interop.Outlook.Application App
{
get { return ThisAddIn.Instance.Application; }
}
#region Debug options
public static string GetFeatureName(Type featureType)
{
return featureType.Name.StripPrefix("Feature");
}
public static string GetDebugTokens(Type featureType)
{
return DebugOptions.GetOptions(GetFeatureName(featureType));
}
public static bool IsEnabled(Type featureType)
{
bool defaultEnabled = !typeof(FeatureDisabled).IsAssignableFrom(featureType);
return DebugOptions.GetOption(GetFeatureName(featureType),
defaultEnabled ? DebugOptions.ENABLED : DebugOptions.FEATURE_DISABLED_DEFAULT);
}
public ValueType GetOption<ValueType>(DebugOptions.Option<ValueType> option)
{
return DebugOptions.GetOption(Name, option);
}
public static ValueType GetOption<ValueType>(Type featureType, DebugOptions.Option<ValueType> option)
{
return DebugOptions.GetOption(GetFeatureName(featureType), option);
}
public void SetOption<ValueType>(DebugOptions.Option<ValueType> option, ValueType value)
{
DebugOptions.SetOption(Name, option, value);
}
public static void SetOption<ValueType>(Type featureType, DebugOptions.Option<ValueType> option, ValueType value)
{
DebugOptions.SetOption(GetFeatureName(featureType), option, value);
}
[AcaciaOption("Completely enables or disables the feature. Note that if the feature is enabled, it's components may still be disabled")]
virtual public bool Enabled
{
get { return GetOption(DebugOptions.ENABLED); }
set { SetOption(DebugOptions.ENABLED, value); }
}
#endregion
#region Outlook UI
/// <summary>
/// Returns the Outlook UI. May be null if modifications to the UI are disabled.
/// </summary>
private OutlookUI OutlookUI
{
get { return ThisAddIn.Instance.OutlookUI; }
}
/// <summary>
/// Helper which registers only if allowed through options
/// </summary>
/// <returns></returns>
public RibbonButton RegisterButton(FeatureWithRibbon feature, string id, bool large, System.Action callback,
ZPushBehaviour zpushBehaviour = ZPushBehaviour.None)
{
if (OutlookUI == null || !UI_Ribbon || !GlobalOptions.INSTANCE.UI_Ribbon)
return null;
return OutlookUI.Register(new RibbonButton(feature, id, large, callback, zpushBehaviour));
}
public RibbonToggleButton RegisterToggleButton(FeatureWithRibbon feature, string id, bool large, System.Action callback,
ZPushBehaviour zpushBehaviour = ZPushBehaviour.None)
{
if (OutlookUI == null || !UI_Ribbon || !GlobalOptions.INSTANCE.UI_Ribbon)
return null;
return OutlookUI.Register(new RibbonToggleButton(feature, id, large, callback, zpushBehaviour));
}
public MenuItem<ItemType> RegisterMenuItem<ItemType>(FeatureWithContextMenu feature, string id, string menuId, System.Action<ItemType> callback,
ZPushBehaviour zpushBehaviour = ZPushBehaviour.None)
where ItemType : IBase
{
if (OutlookUI == null || !UI_ContextMenu || !GlobalOptions.INSTANCE.UI_ContextMenu)
return null;
if (menuId == null)
menuId = GetDefaultMenuId<ItemType>();
return OutlookUI.Register(new MenuItem<ItemType>(feature, id, menuId, callback, zpushBehaviour));
}
private string GetDefaultMenuId<ItemType>()
where ItemType : IBase
{
if (typeof(ItemType) == typeof(IFolder))
return "ContextMenuFolder";
else
throw new System.Exception("Unknown context menu: " + typeof(ItemType));
}
[AcaciaOption("Enables or disables modifications to the Outlook UI for this feature." +
"Note that where applicable, the Ribbon and Context Menu options also control UI modifications.",
Interface = typeof(FeatureWithUI))]
virtual public bool UI
{
get { return GetOption(DebugOptions.OUTLOOK_UI); }
set { SetOption(DebugOptions.OUTLOOK_UI, value); }
}
[AcaciaOption("Enables or disables modifications to the Outlook Ribbon for this feature." +
"Note that if the UI option is disabled, Ribbon modifications will not be made either.",
Interface = typeof(FeatureWithRibbon))]
virtual public bool UI_Ribbon
{
get { return GetOption(DebugOptions.OUTLOOK_UI_RIBBON); }
set { SetOption(DebugOptions.OUTLOOK_UI_RIBBON, value); }
}
[AcaciaOption("Enables or disables modifications to the Outlook Context Menus for this feature." +
"Note that if the UI option is disabled, Context Menu modifications will not be made either.",
Interface = typeof(FeatureWithContextMenu))]
virtual public bool UI_ContextMenu
{
get { return GetOption(DebugOptions.OUTLOOK_UI_CONTEXT_MENU); }
set { SetOption(DebugOptions.OUTLOOK_UI_CONTEXT_MENU, value); }
}
#endregion
#region Event helpers
private static MailEvents _mailEvents;
protected static MailEvents MailEvents
{
get
{
if (_mailEvents == null)
_mailEvents = new MailEvents(App);
return _mailEvents;
}
}
protected ZPushWatcher Watcher
{
get
{
return ThisAddIn.Instance.Watcher;
}
}
#endregion
#region Startup
/// <summary>
/// Invoked when the feature is started. The application object is accessible through
/// App
/// </summary>
public virtual void Startup()
{
}
#endregion
#region Z-Push channels
private static ZPushChannels _zPushChannels;
protected static ZPushChannels ZPushChannels
{
get
{
if (_zPushChannels == null)
_zPushChannels = new ZPushChannels(ThisAddIn.Instance.Watcher);
return _zPushChannels;
}
}
#endregion
#region Debug support
[Browsable(false)]
public string LogContextId { get { return Name; } }
public virtual string ToDebugString()
{
return "";
}
#endregion
}
}

View File

@ -0,0 +1,41 @@
/// 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;
namespace Acacia.Features
{
/// <summary>
/// Base class for a feature that is disabled unless specifically enabled
/// </summary>
abstract public class FeatureDisabled : Feature
{
#region Debug options
[AcaciaOption("Completely enables or disables the feature. Note that if the feature is enabled, it's components may still be disabled")]
override public bool Enabled
{
get { return GetOption(DebugOptions.FEATURE_DISABLED_DEFAULT); }
set { SetOption(DebugOptions.FEATURE_DISABLED_DEFAULT, value); }
}
#endregion
}
}

View File

@ -0,0 +1,32 @@
/// 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;
namespace Acacia.Features
{
/// <summary>
/// Marker interfaces for features that display UIs. Implementing these interfaces automatically adds the
/// debug options to control the UI.
/// </summary>
public interface FeatureWithUI : LogContext {}
public interface FeatureWithRibbon : FeatureWithUI { };
public interface FeatureWithContextMenu : FeatureWithUI { };
}

View File

@ -0,0 +1,40 @@
/// 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;
namespace Acacia.Features
{
public static class Features
{
public static readonly Type[] FEATURES =
{
typeof(ReplyFlags.FeatureReplyFlags),
typeof(OutOfOffice.FeatureOutOfOffice),
typeof(SharedFolders.FeatureSharedFolders),
typeof(WebApp.FeatureWebApp),
typeof(FreeBusy.FeatureFreeBusy),
typeof(GAB.FeatureGAB),
typeof(Notes.FeatureNotes),
typeof(SendAs.FeatureSendAs),
typeof(DebugSupport.FeatureDebugSupport)
};
}
}

View File

@ -0,0 +1,210 @@
/// 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 Acacia.Features.GAB;
using Acacia.Stubs;
using Acacia.UI;
using Acacia.Utils;
using Acacia.ZPush;
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Acacia.Features.FreeBusy
{
[AcaciaOption("Provides free/busy information on users in the Global Adress Book to schedule meetings.")]
public class FeatureFreeBusy : Feature
{
public FeatureFreeBusy()
{
}
public override void Startup()
{
Thread thread = new Thread(() =>
{
try
{
Worker();
}
catch (System.Exception e)
{
Logger.Instance.Error(this, "Unhandled exception: {0}", e);
}
});
thread.Name = "FreeBusy";
thread.Start();
}
#region Settings
public override FeatureSettings GetSettings()
{
return new FreeBusySettings(this);
}
public ZPushAccounts Accounts
{
get { return Watcher.Accounts; }
}
private const string REG_DOGABLOOKUP = "GABLookup";
private const string REG_DEFAULTACCOUNT = "Default";
public bool DoGABLookup
{
get
{
return RegistryUtil.GetConfigValue<int>(Name, REG_DOGABLOOKUP, 1) != 0;
}
set
{
RegistryUtil.SetConfigValue(Name, REG_DOGABLOOKUP, value ? 1 : 0, RegistryValueKind.DWord);
}
}
public ZPushAccount DefaultAccount
{
get
{
string val = RegistryUtil.GetConfigValue<string>(Name, REG_DEFAULTACCOUNT, null);
if (!string.IsNullOrEmpty(val))
{
ZPushAccount account = Accounts.GetAccount(val);
if (account != null)
return account;
}
// Fall back to the first one
return Accounts.GetAccounts().FirstOrDefault();
}
set
{
RegistryUtil.SetConfigValue(Name, REG_DEFAULTACCOUNT, value == null ? "" : value.SmtpAddress, RegistryValueKind.String);
}
}
#endregion
private const string REG_KEY = @"Options\Calendar\Internet Free/Busy";
private const string REG_VALUE = @"Read URL";
internal const string URL_IDENTIFIER = "/zpush/";
private const int DEFAULT_PORT = 18632;
private const string URL_PREFIX = @"http://127.0.0.1:{0}" + URL_IDENTIFIER;
private const string URL = URL_PREFIX + "%NAME%@%SERVER%";
private void Worker()
{
Port = DEFAULT_PORT;
// Register URL
using (RegistryKey key = OutlookRegistryUtils.OpenOutlookKey(REG_KEY, Microsoft.Win32.RegistryKeyPermissionCheck.ReadWriteSubTree))
{
if (key != null)
{
string oldURL = key.GetValueString(REG_VALUE);
if (string.IsNullOrWhiteSpace(oldURL) || oldURL.Contains(URL_IDENTIFIER))
key.SetValue(REG_VALUE, string.Format(URL, Port));
}
}
FreeBusyServer server = new FreeBusyServer(this);
// Run
TcpListener listener = new TcpListener(IPAddress.Loopback, Port);
listener.Start();
for (;;)
{
Interlocked.Increment(ref _iterationCount);
try
{
for (;;)
{
// Wait for a connection
TcpClient client = listener.AcceptTcpClient();
Interlocked.Increment(ref _requestCount);
// And handle it in the UI thread to allow GAB access
Tasks.Task(this, "FreeBusyHandler", () => server.HandleRequest(client));
}
}
catch (Exception e)
{
Logger.Instance.Error(this, "Error in FreeBusy server: {0}", e);
}
}
}
#region Debug
public int Port { get; private set; }
private long _requestCount;
public long RequestCount
{
get { return Interlocked.Read(ref _requestCount); }
}
private long _iterationCount;
public long IterationCount
{
get { return Interlocked.Read(ref _iterationCount); }
}
#endregion
internal ZPushAccount FindZPushAccount(string username)
{
// Search through GABs
if (DoGABLookup)
{
FeatureGAB gab = ThisAddIn.Instance.GetFeature<FeatureGAB>();
if (gab != null)
{
foreach (GABHandler handler in gab.GABHandlers)
{
ZPushAccount account = handler.ActiveAccount;
if (account != null && handler.Contacts != null)
{
// Look for the email address. If found, use the account associated with the GAB
ISearch<IContactItem> search = handler.Contacts.Search<IContactItem>();
search.AddField("urn:schemas:contacts:email1").SetOperation(SearchOperation.Equal, username);
using (IItem result = search.SearchOne())
{
if (result != null)
return account;
}
}
}
}
}
// Fall back to default account
return DefaultAccount;
}
}
}

View File

@ -0,0 +1,158 @@
/// 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 Acacia.Features.GAB;
using Acacia.Stubs;
using Acacia.Utils;
using Acacia.ZPush;
using Acacia.ZPush.Connect;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace Acacia.Features.FreeBusy
{
public class FreeBusyServer
{
private readonly FeatureFreeBusy _freeBusy;
private readonly int _port;
private readonly Regex _httpRequest;
public FreeBusyServer(FeatureFreeBusy freeBusy)
{
this._freeBusy = freeBusy;
this._port = freeBusy.Port;
this._httpRequest = new Regex(@"^GET " + FeatureFreeBusy.URL_IDENTIFIER + @"([^ ]+) HTTP/(\d.\d)$");
}
public void HandleRequest(TcpClient client)
{
try
{
using (client)
{
StreamWriter writer = new StreamWriter(client.GetStream());
StreamReader reader = new StreamReader(client.GetStream());
try
{
// Read the request
string s = reader.ReadLine();
Match m = _httpRequest.Match(s);
if (!m.Success)
{
Logger.Instance.Trace(this, "Invalid request: {0}", s);
throw new InvalidOperationException();
}
string username = m.Groups[1].Value;
Logger.Instance.Trace(this, "REQUEST: {0} -> {1}, {2}", s, m.Groups[1], m.Groups[2]);
// Headers
for (;;)
{
s = reader.ReadLine();
if (string.IsNullOrEmpty(s))
break;
}
// Write response
FetchData(username, writer);
}
catch (InvalidOperationException)
{
writer.Write("HTTP/1.0 404 Not found\r\nConnection: close\r\n\r\n");
}
catch (Exception e)
{
Logger.Instance.Error(this, "Error in FreeBusy worker: {0}", e);
writer.Write("HTTP/1.0 404 Not found\r\nConnection: close\r\n\r\n");
}
writer.Flush();
}
}
catch(Exception e)
{
Logger.Instance.Error(this, "Error in FreeBusy worker: {0}", e);
}
}
private void FetchData(string username, StreamWriter output)
{
// Request the data from the ZPush server
ZPushConnection connection = new ZPushConnection(_freeBusy.FindZPushAccount(username), new System.Threading.CancellationToken(false));
// Include yesterday in the request, outlook shows it by default
var request = new ActiveSync.ResolveRecipientsRequest(username,
DateTime.Today.AddDays(-1),
DateTime.Today.AddMonths(6));
var response = connection.Execute(request);
// If there is no FreeBusy data, return 404
if (response?.FreeBusy == null)
{
throw new InvalidOperationException();
}
Logger.Instance.Trace(this, "Writing response");
// Encode the response in vcard format
output.WriteLine("HTTP/1.0 200 OK");
output.WriteLine("Content-Type: text/vcard");
output.WriteLine("Connection: close");
output.WriteLine("");
output.WriteLine("BEGIN:VCALENDAR");
output.WriteLine("PRODID:-//ZPush//EN");
output.WriteLine("VERSION:2.0");
output.WriteLine("BEGIN:VFREEBUSY");
output.WriteLine("ORGANIZER:" + username);
output.WriteLine(string.Format("URL:http://127.0.0.1:{0}{1}{2}", _port, FeatureFreeBusy.URL_IDENTIFIER, username));
output.WriteLine(string.Format("DTSTAMP:{0:" + Constants.DATE_ISO_8601 + "}", DateTime.Now));
output.WriteLine(string.Format("DTSTART:{0:" + Constants.DATE_ISO_8601 + "}", response.FreeBusy.StartTime));
output.WriteLine(string.Format("DTEND:{0:" + Constants.DATE_ISO_8601 + "}", response.FreeBusy.EndTime));
foreach(ActiveSync.FreeBusyData data in response.FreeBusy)
{
if (data.Type != ActiveSync.FreeBusyType.Free)
{
string freeBusy = string.Format("FREEBUSY;FBTYPE={2}:{0:" + Constants.DATE_ISO_8601 + "}/{1:" + Constants.DATE_ISO_8601 + "}",
data.Start, data.End, MapType(data.Type));
output.WriteLine(freeBusy);
}
}
output.WriteLine("END:VFREEBUSY");
output.WriteLine("END:VCALENDAR");
}
private object MapType(ActiveSync.FreeBusyType type)
{
switch(type)
{
case ActiveSync.FreeBusyType.Free: return "FREE";
case ActiveSync.FreeBusyType.Busy: return "BUSY";
case ActiveSync.FreeBusyType.Tentative: return "BUSY-TENTATIVE";
case ActiveSync.FreeBusyType.OutOfOffice: return "BUSY-UNAVAILABLE";
default:
return "BUSY";
}
}
}
}

View File

@ -0,0 +1,89 @@
namespace Acacia.Features.FreeBusy
{
partial class FreeBusySettings
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FreeBusySettings));
this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel();
this.checkGABLookup = new System.Windows.Forms.CheckBox();
this.labelUseAccount = new System.Windows.Forms.Label();
this.comboDefaultAccount = new System.Windows.Forms.ComboBox();
this.tableLayoutPanel1.SuspendLayout();
this.SuspendLayout();
//
// tableLayoutPanel1
//
resources.ApplyResources(this.tableLayoutPanel1, "tableLayoutPanel1");
this.tableLayoutPanel1.Controls.Add(this.checkGABLookup, 0, 0);
this.tableLayoutPanel1.Controls.Add(this.labelUseAccount, 0, 1);
this.tableLayoutPanel1.Controls.Add(this.comboDefaultAccount, 1, 1);
this.tableLayoutPanel1.Name = "tableLayoutPanel1";
//
// checkGABLookup
//
resources.ApplyResources(this.checkGABLookup, "checkGABLookup");
this.tableLayoutPanel1.SetColumnSpan(this.checkGABLookup, 2);
this.checkGABLookup.Name = "checkGABLookup";
this.checkGABLookup.UseVisualStyleBackColor = true;
this.checkGABLookup.CheckedChanged += new System.EventHandler(this.checkGABLookup_CheckedChanged);
//
// labelUseAccount
//
resources.ApplyResources(this.labelUseAccount, "labelUseAccount");
this.labelUseAccount.Name = "labelUseAccount";
//
// comboDefaultAccount
//
resources.ApplyResources(this.comboDefaultAccount, "comboDefaultAccount");
this.comboDefaultAccount.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.comboDefaultAccount.FormattingEnabled = true;
this.comboDefaultAccount.Name = "comboDefaultAccount";
this.comboDefaultAccount.SelectedIndexChanged += new System.EventHandler(this.comboDefaultAccount_SelectedIndexChanged);
//
// FreeBusySettings
//
resources.ApplyResources(this, "$this");
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = System.Drawing.SystemColors.Window;
this.Controls.Add(this.tableLayoutPanel1);
this.Name = "FreeBusySettings";
this.tableLayoutPanel1.ResumeLayout(false);
this.tableLayoutPanel1.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;
private System.Windows.Forms.CheckBox checkGABLookup;
private System.Windows.Forms.Label labelUseAccount;
private System.Windows.Forms.ComboBox comboDefaultAccount;
}
}

View File

@ -0,0 +1,81 @@
/// 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.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Acacia.ZPush;
using Acacia.UI;
namespace Acacia.Features.FreeBusy
{
public partial class FreeBusySettings : FeatureSettings
{
private readonly FeatureFreeBusy _feature;
public override Feature Feature
{
get
{
return _feature;
}
}
public FreeBusySettings(FeatureFreeBusy feature = null)
{
this._feature = feature;
InitializeComponent();
// Allow null feature for designer
if (feature != null)
{
checkGABLookup.Checked = feature.DoGABLookup;
foreach (ZPushAccount account in feature.Accounts.GetAccounts())
comboDefaultAccount.Items.Add(account);
comboDefaultAccount.SelectedItem = feature.DefaultAccount;
}
}
override public void Apply()
{
_feature.DefaultAccount = (ZPushAccount)comboDefaultAccount.SelectedItem;
_feature.DoGABLookup = checkGABLookup.Checked;
}
private void CheckDirty()
{
Dirty = checkGABLookup.Checked != _feature.DoGABLookup ||
comboDefaultAccount.SelectedItem != _feature.DefaultAccount;
}
private void checkGABLookup_CheckedChanged(object sender, EventArgs e)
{
CheckDirty();
}
private void comboDefaultAccount_SelectedIndexChanged(object sender, EventArgs e)
{
CheckDirty();
}
}
}

View File

@ -0,0 +1,279 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="mscorlib" name="mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="tableLayoutPanel1.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="tableLayoutPanel1.AutoSizeMode" type="System.Windows.Forms.AutoSizeMode, System.Windows.Forms">
<value>GrowAndShrink</value>
</data>
<data name="tableLayoutPanel1.ColumnCount" type="System.Int32, mscorlib">
<value>2</value>
</data>
<data name="checkGABLookup.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="checkGABLookup.Location" type="System.Drawing.Point, System.Drawing">
<value>2, 2</value>
</data>
<data name="checkGABLookup.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>2, 2, 2, 2</value>
</data>
<data name="checkGABLookup.Size" type="System.Drawing.Size, System.Drawing">
<value>222, 17</value>
</data>
<data name="checkGABLookup.TabIndex" type="System.Int32, mscorlib">
<value>0</value>
</data>
<data name="checkGABLookup.Text" xml:space="preserve">
<value>Look up contacts in Global Address Book</value>
</data>
<data name="&gt;&gt;checkGABLookup.Name" xml:space="preserve">
<value>checkGABLookup</value>
</data>
<data name="&gt;&gt;checkGABLookup.Type" xml:space="preserve">
<value>System.Windows.Forms.CheckBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;checkGABLookup.Parent" xml:space="preserve">
<value>tableLayoutPanel1</value>
</data>
<data name="&gt;&gt;checkGABLookup.ZOrder" xml:space="preserve">
<value>0</value>
</data>
<data name="labelUseAccount.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
<value>Top, Bottom, Left</value>
</data>
<data name="labelUseAccount.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="labelUseAccount.Location" type="System.Drawing.Point, System.Drawing">
<value>2, 30</value>
</data>
<data name="labelUseAccount.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>2, 0, 2, 0</value>
</data>
<data name="labelUseAccount.Size" type="System.Drawing.Size, System.Drawing">
<value>74, 30</value>
</data>
<data name="labelUseAccount.TabIndex" type="System.Int32, mscorlib">
<value>1</value>
</data>
<data name="labelUseAccount.Text" xml:space="preserve">
<value>Use account: </value>
</data>
<data name="labelUseAccount.TextAlign" type="System.Drawing.ContentAlignment, System.Drawing">
<value>MiddleLeft</value>
</data>
<data name="&gt;&gt;labelUseAccount.Name" xml:space="preserve">
<value>labelUseAccount</value>
</data>
<data name="&gt;&gt;labelUseAccount.Type" xml:space="preserve">
<value>System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;labelUseAccount.Parent" xml:space="preserve">
<value>tableLayoutPanel1</value>
</data>
<data name="&gt;&gt;labelUseAccount.ZOrder" xml:space="preserve">
<value>1</value>
</data>
<data name="comboDefaultAccount.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
<value>Top, Bottom, Left, Right</value>
</data>
<data name="comboDefaultAccount.Location" type="System.Drawing.Point, System.Drawing">
<value>80, 32</value>
</data>
<data name="comboDefaultAccount.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>2, 2, 2, 2</value>
</data>
<data name="comboDefaultAccount.Size" type="System.Drawing.Size, System.Drawing">
<value>172, 21</value>
</data>
<data name="comboDefaultAccount.TabIndex" type="System.Int32, mscorlib">
<value>2</value>
</data>
<data name="&gt;&gt;comboDefaultAccount.Name" xml:space="preserve">
<value>comboDefaultAccount</value>
</data>
<data name="&gt;&gt;comboDefaultAccount.Type" xml:space="preserve">
<value>System.Windows.Forms.ComboBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;comboDefaultAccount.Parent" xml:space="preserve">
<value>tableLayoutPanel1</value>
</data>
<data name="&gt;&gt;comboDefaultAccount.ZOrder" xml:space="preserve">
<value>2</value>
</data>
<data name="tableLayoutPanel1.Dock" type="System.Windows.Forms.DockStyle, System.Windows.Forms">
<value>Fill</value>
</data>
<data name="tableLayoutPanel1.Location" type="System.Drawing.Point, System.Drawing">
<value>0, 0</value>
</data>
<data name="tableLayoutPanel1.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>2, 2, 2, 2</value>
</data>
<data name="tableLayoutPanel1.RowCount" type="System.Int32, mscorlib">
<value>2</value>
</data>
<data name="tableLayoutPanel1.Size" type="System.Drawing.Size, System.Drawing">
<value>254, 60</value>
</data>
<data name="tableLayoutPanel1.TabIndex" type="System.Int32, mscorlib">
<value>0</value>
</data>
<data name="&gt;&gt;tableLayoutPanel1.Name" xml:space="preserve">
<value>tableLayoutPanel1</value>
</data>
<data name="&gt;&gt;tableLayoutPanel1.Type" xml:space="preserve">
<value>System.Windows.Forms.TableLayoutPanel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;tableLayoutPanel1.Parent" xml:space="preserve">
<value>$this</value>
</data>
<data name="&gt;&gt;tableLayoutPanel1.ZOrder" xml:space="preserve">
<value>0</value>
</data>
<data name="tableLayoutPanel1.LayoutSettings" type="System.Windows.Forms.TableLayoutSettings, System.Windows.Forms">
<value>&lt;?xml version="1.0" encoding="utf-16"?&gt;&lt;TableLayoutSettings&gt;&lt;Controls&gt;&lt;Control Name="checkGABLookup" Row="0" RowSpan="1" Column="0" ColumnSpan="2" /&gt;&lt;Control Name="labelUseAccount" Row="1" RowSpan="1" Column="0" ColumnSpan="1" /&gt;&lt;Control Name="comboDefaultAccount" Row="1" RowSpan="1" Column="1" ColumnSpan="1" /&gt;&lt;/Controls&gt;&lt;Columns Styles="AutoSize,0,Percent,100" /&gt;&lt;Rows Styles="Percent,50,Percent,50" /&gt;&lt;/TableLayoutSettings&gt;</value>
</data>
<metadata name="$this.Localizable" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<data name="$this.AutoScaleDimensions" type="System.Drawing.SizeF, System.Drawing">
<value>6, 13</value>
</data>
<data name="$this.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="$this.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>2, 2, 2, 2</value>
</data>
<data name="$this.Size" type="System.Drawing.Size, System.Drawing">
<value>254, 60</value>
</data>
<data name="&gt;&gt;$this.Name" xml:space="preserve">
<value>FreeBusySettings</value>
</data>
<data name="&gt;&gt;$this.Type" xml:space="preserve">
<value>Acacia.UI.FeatureSettings, Kopano, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null</value>
</data>
</root>

View File

@ -0,0 +1,52 @@
/// 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.Text.RegularExpressions;
using System.Threading.Tasks;
namespace Acacia.Features.GAB
{
struct ChunkIndex
{
private static readonly Regex RE = new Regex(@"^account-(\d+)/(\d+)$");
public int numberOfChunks;
public int chunk;
public static ChunkIndex? Parse(string s)
{
try
{
Match match = RE.Match(s);
if (!match.Success)
return null;
return new ChunkIndex()
{
numberOfChunks = int.Parse(match.Groups[1].Value),
chunk = int.Parse(match.Groups[2].Value)
};
}
catch (Exception)
{
return null;
}
}
}
}

View File

@ -0,0 +1,626 @@
/// 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 Microsoft.Office.Interop.Outlook;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Acacia.Stubs;
using Acacia.Stubs.OutlookWrappers;
using System.Threading;
using System.Collections.Concurrent;
using Acacia.ZPush;
using Acacia.Utils;
using System.ComponentModel;
using System.Windows.Forms;
using Acacia.UI;
using static Acacia.DebugOptions;
namespace Acacia.Features.GAB
{
[AcaciaOption("Provides a Global Address Book for Z-Push accounts.")]
public class FeatureGAB : Feature
{
private readonly Dictionary<string, GABHandler> _gabsByDomainName = new Dictionary<string, GABHandler>();
private readonly HashSet<string> _gabFolders = new HashSet<string>();
private readonly HashSet<string> _domains = new HashSet<string>();
private ZPushLocalStore _store;
private int _processing;
public FeatureGAB()
{
}
public static GABHandler FindGABForAccount(ZPushAccount account)
{
FeatureGAB gab = ThisAddIn.Instance.GetFeature<FeatureGAB>();
if (gab != null)
{
foreach (GABHandler handler in gab.GABHandlers)
{
if (account == handler.ActiveAccount)
{
return handler;
}
}
}
return null;
}
public override void Startup()
{
MailEvents.BeforeDelete += SuppressEventHandler_Delete;
MailEvents.Write += SuppressEventHandler_Modify;
Watcher.AccountDiscovered += AccountDiscovered;
Watcher.AccountRemoved += AccountRemoved;
Watcher.AccountsScanned += AccountsScanned;
}
#region Settings
public override FeatureSettings GetSettings()
{
return new GABSettings(this);
}
#endregion
#region Debug options
[AcaciaOption("Disables the processing of the folder containing the GAB synchronization queue. " +
"This should only be disabled for debug purposes.")]
public bool ProcessFolder
{
get { return GetOption(OPTION_PROCESS_FOLDER); }
set { SetOption(OPTION_PROCESS_FOLDER, value); }
}
private static readonly BoolOption OPTION_PROCESS_FOLDER = new BoolOption("ProcessFolder", true);
[AcaciaOption("Disables the processing of items in the GAB synchronization queue. " +
"This should only be disabled for debug purposes.")]
public bool ProcessItems
{
get { return GetOption(OPTION_PROCESS_ITEMS); }
set { SetOption(OPTION_PROCESS_ITEMS, value); }
}
private static readonly BoolOption OPTION_PROCESS_ITEMS = new BoolOption("ProcessItems", true);
[AcaciaOption("Disables the second stage of processing of items in the GAB synchronization queue. " +
"This should only be disabled for debug purposes")]
public bool ProcessItems2
{
get { return GetOption(OPTION_PROCESS_ITEMS_2); }
set { SetOption(OPTION_PROCESS_ITEMS_2, value); }
}
private static readonly BoolOption OPTION_PROCESS_ITEMS_2 = new BoolOption("ProcessItems2", true);
[AcaciaOption("Disables the processing of messages containing GAB contacts. " +
"This should only be disabled for debug purposes.")]
public bool ProcessMessage
{
get { return GetOption(OPTION_PROCESS_MESSAGE); }
set { SetOption(OPTION_PROCESS_MESSAGE, value); }
}
private static readonly BoolOption OPTION_PROCESS_MESSAGE = new BoolOption("ProcessMessage", true);
[AcaciaOption("If disabled, contacts are not created from incoming GAB messages. " +
"This should only be disabled for debug purposes.")]
public bool CreateContacts
{
get { return GetOption(OPTION_CREATE_CONTACTS); }
set { SetOption(OPTION_CREATE_CONTACTS, value); }
}
private static readonly BoolOption OPTION_CREATE_CONTACTS = new BoolOption("CreateContacts", true);
[AcaciaOption("If disabled, groups are not created from incoming GAB messages. " +
"This should only be disabled for debug purposes.")]
public bool CreateGroups
{
get { return GetOption(OPTION_CREATE_GROUPS); }
set { SetOption(OPTION_CREATE_GROUPS, value); }
}
private static readonly BoolOption OPTION_CREATE_GROUPS = new BoolOption("CreateGroups", true);
[AcaciaOption("If disabled, group members are not parsed from incoming GAB messages. " +
"This should only be disabled for debug purposes.")]
public bool GroupMembers
{
get { return GetOption(OPTION_GROUP_MEMBERS); }
set { SetOption(OPTION_GROUP_MEMBERS, value); }
}
private static readonly BoolOption OPTION_GROUP_MEMBERS = new BoolOption("GroupMembers", true);
[AcaciaOption("If disabled, group members are not added to groups created from GAB messages. " +
"This should only be disabled for debug purposes.")]
public bool GroupMembersAdd
{
get { return GetOption(OPTION_GROUP_MEMBERS_ADD); }
set { SetOption(OPTION_GROUP_MEMBERS_ADD, value); }
}
private static readonly BoolOption OPTION_GROUP_MEMBERS_ADD = new BoolOption("GroupMembersAdd", true);
[AcaciaOption("If disabled, groups that are members of other groups are not added to the parent group. " +
"This should only be disabled for debug purposes.")]
public bool NestedGroups
{
get { return GetOption(OPTION_NESTED_GROUPS); }
set { SetOption(OPTION_NESTED_GROUPS, value); }
}
private static readonly BoolOption OPTION_NESTED_GROUPS = new BoolOption("NestedGroups", true);
[AcaciaOption("If this option is enabled, the GAB checks for unused local folders and removes them. " +
"If disabled, the unused local folders are left alone. The only reason GAB folders " +
"can become unused is if an account is removed, or if the GAB is removed from the server.")]
public bool CheckUnused
{
get { return GetOption(OPTION_CHECK_UNUSED); }
set { SetOption(OPTION_CHECK_UNUSED, value); }
}
private static readonly BoolOption OPTION_CHECK_UNUSED = new BoolOption("CheckUnused", true);
#endregion
#region Modification suppression
internal void BeginProcessing()
{
++_processing;
}
internal void EndProcessing()
{
--_processing;
}
private void SuppressEventHandler_Delete(IItem item, ref bool cancel)
{
SuppressEventHandler(item, false, ref cancel);
}
private void SuppressEventHandler_Modify(IItem item, ref bool cancel)
{
SuppressEventHandler(item, true, ref cancel);
}
private void SuppressEventHandler(IItem item, bool findInspector, ref bool cancel)
{
// Allow events from processing
if (_processing == 0)
{
// Check parent folder is a GAB contacts folder
if (_gabFolders.Contains(item.ParentEntryId) && IsGABItem(item))
{
DoSuppressEvent(findInspector ? item : null, ref cancel);
}
}
}
private bool IsGABItem(IItem item)
{
// For some reason, Outlook creates a meeting request in the GAB folder. Modifying that should be allowed
return item is IContactItem || item is IDistributionList;
}
private void SuppressMoveEventHandler(IFolder src, IItem item, IFolder target, ref bool cancel)
{
// Allow events from processing
if (_processing == 0)
{
// Always disallow moves from out of the folder
DoSuppressEvent(null, ref cancel);
}
}
/// <summary>
///
/// </summary>
/// <param name="item">If specified, the function will attempt to find an inspector for the item and close it.</param>
/// <param name="cancel"></param>
private void DoSuppressEvent(IItem item, ref bool cancel)
{
if (item != null)
{
foreach (Inspector inspector in App.Inspectors)
{
if (item.EntryId == inspector.CurrentItem.EntryID)
{
break;
}
}
}
MessageBox.Show(StringUtil.GetResourceString("GABEvent_Body"),
StringUtil.GetResourceString("GABEvent_Title"),
MessageBoxButtons.OK,
MessageBoxIcon.Warning
);
cancel = true;
}
#endregion
#region Resync
internal void FullResync()
{
try
{
Logger.Instance.Trace(this, "FullResync begin: {0}", _processing);
BeginProcessing();
// Delete any contacts folders in the local store
using (ZPushLocalStore store = ZPushLocalStore.GetInstance(App))
{
if (store != null)
{
using (IFolder root = store.RootFolder)
{
foreach (IFolder folder in root.GetSubFolders<IFolder>())
{
// TODO: let enumerator handle this
using (folder)
{
try
{
if (IsGABContactsFolder(folder))
{
Logger.Instance.Debug(this, "FullResync: Deleting contacts folder: {0}", folder.Name);
folder.Delete();
}
}
catch (System.Exception e)
{
Logger.Instance.Error(this, "FullResync: Exception deleting contacts folder: {0}", e);
}
}
}
}
}
}
// Do the resync
foreach (GABHandler gab in _gabsByDomainName.Values)
{
Logger.Instance.Debug(this, "FullResync: Starting resync: {0}", gab.DisplayName);
Tasks.Task(this, "FullResync", () => gab.FullResync());
}
}
finally
{
EndProcessing();
Logger.Instance.Trace(this, "FullResync done: {0}", _processing);
}
}
#endregion
#region Contacts folders
[Browsable(false)]
public IEnumerable<GABHandler> GABHandlers
{
get
{
return _gabsByDomainName.Values;
}
}
private IAddressBook FindGABForDomain(IFolder root, string domain)
{
// Scan all subfolders for the GAB
foreach (IAddressBook subfolder in root.GetSubFolders<IAddressBook>())
{
try
{
if (subfolder.DefaultMessageClass == OutlookConstants.MESSAGE_CLASS_CONTACTS)
{
GABInfo gabInfo = GABInfo.Get(subfolder);
if (gabInfo != null && gabInfo.IsForDomain(domain))
{
Logger.Instance.Debug(this, "Found existing GAB: {0}", gabInfo);
return subfolder;
}
}
}
catch (System.Exception e)
{
Logger.Instance.Warning(this, "Exception scanning GABs: {0}", e);
}
Logger.Instance.Debug(this, "Skipping GAB folder: {0}", subfolder.Name);
subfolder.Dispose();
}
return null;
}
private IAddressBook CreateGABContacts(string domainName)
{
if (_store != null)
{
_store.Dispose();
_store = null;
}
_store = ZPushLocalStore.GetInstance(App);
if (_store == null)
return null;
// Try to find the existing GAB
using (IFolder root = _store.RootFolder)
{
IAddressBook gab = FindGABForDomain(root, domainName);
if (gab == null)
{
Logger.Instance.Debug(this, "Creating new GAB folder for {0}", domainName);
string name = string.Format(Properties.Resources.GAB_FolderFormat, domainName);
gab = root.CreateFolder<IAddressBook>(name);
}
else
{
Logger.Instance.Debug(this, "Found existing GAB folder for {0}", domainName);
}
// The local folders are hidden, unhide tha GAB folder
gab.AttrHidden = false;
// Update admin
_gabFolders.Add(gab.EntryId);
GABInfo gabInfo = GABInfo.Get(gab, domainName);
gabInfo.Store(gab);
// Hook BeforeMove event to prevent modifications
// TODO: use ZPushWatcher for this?
gab.BeforeItemMove += SuppressMoveEventHandler;
return gab;
}
}
private void DisposeGABContacts(IAddressBook gab)
{
// Unhook the event to prevent the gab lingering in memory
gab.BeforeItemMove -= SuppressMoveEventHandler;
}
public static GABInfo GetGABContactsFolderInfo(IFolder folder)
{
if (folder.DefaultMessageClass != OutlookConstants.MESSAGE_CLASS_CONTACTS)
return null;
return GABInfo.Get(folder);
}
public static bool IsGABContactsFolder(IFolder folder)
{
return GetGABContactsFolderInfo(folder) != null;
}
private void AccountDiscovered(ZPushAccount zpush)
{
Logger.Instance.Info(this, "Account discovered: {0}", zpush.DisplayName);
_domains.Add(zpush.DomainName);
zpush.ConfirmedChanged += (z) =>
{
if (zpush.Confirmed == ZPushAccount.ConfirmationType.IsZPush &&
!string.IsNullOrEmpty(zpush.GABFolder))
{
// Set up the Z-Push channel listener
ZPushChannel channel = ZPushChannels.Get(this, zpush, zpush.GABFolder);
channel.Available += ZPushChannelAvailable;
channel.Start();
}
};
}
private void AccountRemoved(ZPushAccount account)
{
try
{
foreach (GABHandler gab in _gabsByDomainName.Values)
{
gab.RemoveAccount(account);
}
CheckGABRemoved();
}
catch (System.Exception e)
{
Logger.Instance.Error(this, "Exception in AccountRemoved: {0}", e);
}
}
private void AccountsScanned()
{
try
{
Logger.Instance.Debug(this, "Accounts scanned");
CheckGABUnused();
CheckGABRemoved();
}
catch(System.Exception e)
{
Logger.Instance.Error(this, "Exception in AccountsScanned: {0}", e);
}
}
/// <summary>
/// Checks and removes any GAB folders that are not registered to accounts. This
/// happens if an account is removed while Outlook is closed.
/// </summary>
private void CheckGABUnused()
{
if (!CheckUnused)
return;
if (_store != null)
{
_store.Dispose();
_store = null;
}
_store = ZPushLocalStore.GetInstance(App);
if (_store == null)
return;
bool deletedSomething = false;
using (IFolder root = _store.RootFolder)
{
foreach (IFolder subfolder in root.GetSubFolders<IFolder>())
{
using (subfolder)
{
// Remove any contacts folder that is not registered for GAB
GABInfo info = GetGABContactsFolderInfo(subfolder);
if (info != null && !_domains.Contains(info.Domain))
{
Logger.Instance.Info(this, "Unused GAB folder: {0} - {1}", subfolder.EntryId, subfolder.Name);
try
{
deletedSomething = true;
subfolder.Delete();
}
catch (System.Exception e)
{
Logger.Instance.Error(this, "Error removing GAB folder: {0}", e);
}
}
}
}
}
if (deletedSomething)
EmptyDeletedItems();
}
private void CheckGABRemoved()
{
Logger.Instance.Debug(this, "CheckGABRemoved");
// Find any GABs that no longer have accounts
List<KeyValuePair<string, GABHandler>> remove = new List<KeyValuePair<string, GABHandler>>();
foreach(KeyValuePair<string, GABHandler> entry in _gabsByDomainName)
{
Logger.Instance.Debug(this, "CheckGABRemoved: {0} - {1}", entry.Key, entry.Value.HasAccounts);
if (!entry.Value.HasAccounts)
{
remove.Add(entry);
}
}
// Remove any
if (remove.Count != 0)
{
foreach (KeyValuePair<string, GABHandler> entry in remove)
{
try
{
Logger.Instance.Info(this, "Removing GAB: {0}", entry.Key);
_gabsByDomainName.Remove(entry.Key);
entry.Value.Remove();
}
catch (System.Exception e)
{
Logger.Instance.Error(this, "Exception removing GAB: {0}: {1}", entry.Key, e);
}
}
EmptyDeletedItems();
}
}
private void RegisterGABAccount(ZPushAccount account, IFolder folder)
{
// Determine the domain name
string domain = account.DomainName;
// Could already be registered if there are multiple accounts on the same domain
GABHandler gab;
if (!_gabsByDomainName.TryGetValue(domain, out gab))
{
// Create the handler
gab = new GABHandler(this, (f) => CreateGABContacts(domain), (f) => DisposeGABContacts(f));
_gabsByDomainName.Add(domain, gab);
}
else
{
Logger.Instance.Debug(this, "GAB handler already registered: {0} on {1}", folder, folder.StoreDisplayName);
}
// Register the account with the GAB
gab.AddAccount(account, folder);
// The folder has become available, check the GAB messages
DoProcess(account, gab, null);
// And watch for any new messages
Watcher.WatchItems<IZPushItem>(folder, (item) => DoProcess(account, gab, item), false);
}
#endregion
#region Processing
private void ZPushChannelAvailable(IFolder folder)
{
using (IStore store = folder.Store)
{
Logger.Instance.Debug(this, "Z-Push channel available: {0} on {1}", folder, store.DisplayName);
ZPushAccount account = Watcher.Accounts.GetAccount(folder);
if (account != null)
{
account.LinkedGABFolder(folder);
RegisterGABAccount(account, folder);
}
else
{
Logger.Instance.Warning(this, "Z-Push channel account not found: {0} on {1}", folder, store.DisplayName);
}
Logger.Instance.Debug(this, "Z-Push channel available done");
}
}
private void DoProcess(ZPushAccount account, GABHandler gab, IZPushItem item)
{
// Multiple accounts - and therefore multiple folders - may use the same GAB.
// One process the items from the first associated account
if (account != gab.ActiveAccount)
{
Logger.Instance.Trace(this, "Ignoring GAB message: {0} - {1}", account, item);
return;
}
++_processing;
Logger.Instance.Trace(this, "Processing GAB message: {0} - {1}", account, _processing);
try
{
gab.Process(item);
EmptyDeletedItems();
}
finally
{
Logger.Instance.Trace(this, "Processed GAB message: {0} - {1}", account, _processing);
--_processing;
}
}
private void EmptyDeletedItems()
{
if (_store != null)
_store.EmptyDeletedItems();
}
#endregion
}
}

View File

@ -0,0 +1,693 @@
/// 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.Text.RegularExpressions;
using System.Threading.Tasks;
using Acacia.Stubs;
using Acacia.ZPush;
using Acacia.Utils;
using System.Collections;
using static Acacia.DebugOptions;
namespace Acacia.Features.GAB
{
public class GABHandler : LogContext
{
public string LogContextId { get { return "GAB"; } }
private readonly FeatureGAB _feature;
#region Contacts
private readonly Func<IFolder, IAddressBook> _contactsProvider;
private readonly Action<IAddressBook> _contactsDisposer;
private IAddressBook _contacts;
public IAddressBook Contacts
{
get
{
if (_contacts == null)
{
_contacts = _contactsProvider(Folder);
}
return _contacts;
}
}
private IStorageItem GetIndexItem()
{
return Contacts?.GetStorageItem(Constants.ZPUSH_GAB_INDEX);
}
#endregion
#region Accounts & Folders
public ZPushAccount ActiveAccount
{
get
{
return ThisAddIn.Instance.Watcher.Accounts.GetAccount(Folder);
}
}
/// <summary>
/// The list of accounts that are associated with this GAB.
/// </summary>
private readonly List<ZPushAccount> _accounts = new List<ZPushAccount>();
private readonly List<IFolder> _accountFolders = new List<IFolder>();
private IFolder Folder
{
get
{
if (!HasAccounts)
return null;
return _accountFolders.FirstOrDefault();
}
}
public void AddAccount(ZPushAccount account, IFolder folder)
{
_accounts.Add(account);
_accountFolders.Add(folder);
}
public void RemoveAccount(ZPushAccount account)
{
int i = _accounts.IndexOf(account);
if (i >= 0)
{
_accounts.RemoveAt(i);
_accountFolders.RemoveAt(i);
}
}
internal bool HasAccounts
{
get
{
return _accounts.Count > 0;
}
}
#endregion
public GABHandler(FeatureGAB feature, Func<IFolder, IAddressBook> contactsProvider, Action<IAddressBook> contactsDisposer)
{
this._feature = feature;
this._contactsProvider = contactsProvider;
this._contactsDisposer = contactsDisposer;
}
public string DisplayName
{
get
{
using(IStore store = Folder.Store)
return store.DisplayName;
}
}
#region Processing
public void FullResync()
{
ClearContacts();
Process(null);
}
private void ClearContacts()
{
if (Contacts != null)
{
try
{
Contacts.Delete();
}
catch (Exception e)
{
Logger.Instance.Warning(this, "Error clearing contacts folder for {0}: {1}", DisplayName, e);
// There was an error deleting the contacts folder, try clearing it
using (IStorageItem index = GetIndexItem())
{
index?.Delete();
}
Contacts.Clear();
}
CleanupContactsObject();
}
}
/// <summary>
/// Processes the GAB message(s).
/// </summary>
/// <param name="item">If specified, this item has changed. If null, means a global check should be performed</param>
public void Process(IZPushItem item)
{
try
{
if (item == null)
{
if (Folder != null)
ProcessMessages();
}
else
{
ProcessMessage(item);
}
}
catch(Exception e)
{
Logger.Instance.Error(this, "Exception in GAB.Process: {0}", e);
}
}
private void ProcessMessages()
{
if (!_feature.ProcessFolder)
return;
DetermineSequence();
if (CurrentSequence == null)
return; // No messages to process
if (!_feature.ProcessItems)
return;
// Process the messages
foreach (IItem item in Folder.Items)
{
// TODO: make type-checking iterator?
if (item is IZPushItem)
{
string entryId = item.EntryId;
Logger.Instance.Trace(this, "Checking chunk: {0}", item.Subject);
if (_feature.ProcessItems2)
{
Tasks.Task(_feature, "ProcessChunk", () =>
{
using (IItem item2 = Folder.GetItemById(entryId))
{
if (item2 != null)
ProcessMessage((IZPushItem)item2);
}
});
}
}
}
}
public const string PROP_LAST_PROCESSED = "ZPushLastProcessed";
public const string PROP_SEQUENCE = "ZPushSequence";
public const string PROP_CHUNK = "ZPushChunk";
public const string PROP_GAB_ID = "ZPushId";
public const string PROP_CURRENT_SEQUENCE = "ZPushCurrentSequence";
private void ProcessMessage(IZPushItem item)
{
if (!_feature.ProcessMessage)
return;
// Check if the message is for the current sequence
ChunkIndex? optionalIndex = ChunkIndex.Parse(item.Subject);
if (optionalIndex == null)
{
Logger.Instance.Trace(this, "Not a chunk: {0}", item.Subject);
return;
}
if (optionalIndex.Value.numberOfChunks != CurrentSequence)
{
// Try to update the current sequence; this message may indicate that it has changed
DetermineSequence();
// If it is still not for the current sequence, it's an old message
if (optionalIndex.Value.numberOfChunks != CurrentSequence)
{
Logger.Instance.Trace(this, "Skipping, wrong sequence: {0}", item.Subject);
return;
}
}
ChunkIndex index = optionalIndex.Value;
// Check if the message is up to date
string lastProcessed = GetChunkStateString(index);
if (lastProcessed == item.Location)
{
Logger.Instance.Trace(this, "Already up to date: {0} - {1}", item.Subject, item.Location);
return;
}
// Process it
Logger.Instance.Trace(this, "Processing: {0} - {1} - {2}", item.Subject, item.Location, lastProcessed);
_feature?.BeginProcessing();
try
{
// Delete the old contacts from this chunk
ISearch<IItem> search = Contacts.Search<IItem>();
search.AddField(PROP_SEQUENCE, true).SetOperation(SearchOperation.Equal, index.numberOfChunks);
search.AddField(PROP_CHUNK, true).SetOperation(SearchOperation.Equal, index.chunk);
foreach (IItem oldItem in search.Search())
{
// TODO: Search should handle this, like folder enumeration
using (oldItem)
{
Logger.Instance.Trace(this, "Deleting GAB entry: {0}", oldItem.Subject);
oldItem.Delete();
}
}
// Create the new contacts
ProcessChunkBody(item, index);
// Update the state
SetChunkStateString(index, item.Location);
}
finally
{
_feature?.EndProcessing();
}
}
#endregion
#region Sequence
public int? CurrentSequence
{
get
{
using (IStorageItem index = GetIndexItem())
{
return index?.GetUserProperty<int>(PROP_CURRENT_SEQUENCE)?.Value;
}
}
set
{
using (IStorageItem index = GetIndexItem())
{
if (value != null)
{
index.GetUserProperty<int>(PROP_CURRENT_SEQUENCE, true).Value = value.Value;
}
else
{
index.Delete();
}
}
}
}
private IItem FindNewestChunk()
{
if (Folder == null)
return null;
// Scan a few of the newest items, in case there is some junk in the ZPush folder
// TODO: this shouldn't happen in production.
int i = 0;
foreach(IItem item in Folder.ItemsSorted("LastModificationTime", true))
{
if (ChunkIndex.Parse(item.Subject) != null)
return item;
item.Dispose();
if (i > Constants.ZPUSH_GAB_NEWEST_MAX_CHECK)
return null;
++i;
}
return null;
}
public void DetermineSequence()
{
try
{
// Find the newest chunk
using (IItem newest = FindNewestChunk())
{
if (newest == null)
CurrentSequence = null;
else
{
Logger.Instance.Trace(this, "Newest chunk: {0}", newest.Subject);
ChunkIndex? newestChunkIndex = ChunkIndex.Parse(newest.Subject);
if (!CurrentSequence.HasValue || CurrentSequence.Value != newestChunkIndex?.numberOfChunks)
{
// Sequence has changed. Delete contacts
Logger.Instance.Trace(this, "Rechunked, deleting contacts");
ClearContacts();
// Determine new sequence
if (newestChunkIndex == null)
{
using (IStorageItem index = GetIndexItem())
{
if (index != null)
index.Delete();
}
}
else
{
int numberOfChunks = newestChunkIndex.Value.numberOfChunks;
using (IStorageItem index = GetIndexItem())
{
index.GetUserProperty<int>(PROP_CURRENT_SEQUENCE, true).Value = numberOfChunks;
index.GetUserProperty<string>(PROP_LAST_PROCESSED, true).Value = CreateChunkStateString(numberOfChunks);
index.Save();
}
}
}
}
}
}
catch(Exception e)
{
Logger.Instance.Trace(this, "Exception determining sequence: {0}", e);
// Delete the index item
using (IStorageItem index = GetIndexItem())
index?.Delete();
return;
}
Logger.Instance.Trace(this, "Current sequence: {0}", CurrentSequence);
}
private string CreateChunkStateString(int count)
{
string[] defaultValues = new string[count];
return string.Join(";", defaultValues);
}
private string GetChunkStateString(ChunkIndex index)
{
using (IStorageItem item = GetIndexItem())
{
if (item == null)
return null;
string state = item.GetUserProperty<string>(PROP_LAST_PROCESSED)?.Value;
if (string.IsNullOrEmpty(state))
return null;
string[] parts = state.Split(';');
if (parts.Length != index.numberOfChunks)
{
Logger.Instance.Error(this, "Wrong number of chunks, got {0}, expected {1}: {2}",
parts.Length, index.numberOfChunks, state);
}
return parts[index.chunk];
}
}
private void SetChunkStateString(ChunkIndex index, string partState)
{
using (IStorageItem item = GetIndexItem())
{
string state = item.GetUserProperty<string>(PROP_LAST_PROCESSED)?.Value;
string[] parts;
if (string.IsNullOrEmpty(state))
parts = new string[index.numberOfChunks];
else
parts = state.Split(';');
if (parts.Length != index.numberOfChunks)
{
Logger.Instance.Error(this, "Wrong number of chunks, got {0}, expected {1}: {2}",
parts.Length, index.numberOfChunks, state);
}
parts[index.chunk] = partState;
string combined = string.Join(";", parts);
item.GetUserProperty<string>(PROP_LAST_PROCESSED, true).Value = combined;
item.Save();
}
}
#endregion
#region Message parsing
private ValueType Get<ValueType>(Dictionary<string, object> values, string name)
where ValueType : class
{
object value;
values.TryGetValue(name, out value);
return value as ValueType;
}
private void ProcessChunkBody(IZPushItem item, ChunkIndex index)
{
// Process the body
foreach (var entry in JSONUtils.Deserialise(item.Body))
{
string id = entry.Key;
Dictionary<string, object> value = (Dictionary<string, object>)entry.Value;
Tasks.Task(_feature, "CreateItem", () => CreateObject(index, id, value));
}
}
private void CreateObject(ChunkIndex index, string id, Dictionary<string, object> value)
{
try
{
_feature?.BeginProcessing();
string type = Get<string>(value, "type");
if (type == "contact")
CreateContact(id, value, index, 0);
else if (type == "group")
CreateGroup(id, value, index);
else if (type == "equipment")
CreateContact(id, value, index, OutlookConstants.DT_EQUIPMENT);
else if (type == "room")
CreateContact(id, value, index, OutlookConstants.DT_ROOM);
else
{
Logger.Instance.Warning(this, "Unknown entry type: {0}", type);
}
}
catch (System.Exception e)
{
Logger.Instance.Error(this, "Error creating entry: {0}: {1}", id, e);
}
finally
{
_feature?.EndProcessing();
}
}
private void CreateContact(string id, Dictionary<string, object> value, ChunkIndex index, int resourceType)
{
if (!_feature.CreateContacts)
return;
using (IContactItem contact = Contacts.Create<IContactItem>())
{
Logger.Instance.Trace(this, "Creating contact: {0}", id);
contact.CustomerID = id;
// Create the contact data
if (Get<string>(value, "displayName") != null) contact.FullName = Get<string>(value, "displayName");
if (Get<string>(value, "givenName") != null) contact.FirstName = Get<string>(value, "givenName");
if (Get<string>(value, "initials") != null) contact.Initials = Get<string>(value, "initials");
if (Get<string>(value, "surname") != null) contact.LastName = Get<string>(value, "surname");
if (Get<string>(value, "title") != null) contact.JobTitle = Get<string>(value, "title");
if (Get<string>(value, "smtpAddress") != null)
{
contact.Email1Address = Get<string>(value, "smtpAddress");
contact.Email1AddressType = "SMTP";
}
if (Get<string>(value, "companyName") != null) contact.CompanyName = Get<string>(value, "companyName");
if (Get<string>(value, "officeLocation") != null) contact.OfficeLocation = Get<string>(value, "officeLocation");
if (Get<string>(value, "businessTelephoneNumber") != null) contact.BusinessTelephoneNumber = Get<string>(value, "businessTelephoneNumber");
if (Get<string>(value, "mobileTelephoneNumber") != null) contact.MobileTelephoneNumber = Get<string>(value, "mobileTelephoneNumber");
if (Get<string>(value, "homeTelephoneNumber") != null) contact.HomeTelephoneNumber = Get<string>(value, "homeTelephoneNumber");
if (Get<string>(value, "beeperTelephoneNumber") != null) contact.PagerNumber = Get<string>(value, "beeperTelephoneNumber");
if (Get<string>(value, "primaryFaxNumber") != null) contact.BusinessFaxNumber = Get<string>(value, "primaryFaxNumber");
if (Get<string>(value, "organizationalIdNumber") != null) contact.OrganizationalIDNumber = Get<string>(value, "organizationalIdNumber");
if (Get<string>(value, "postalAddress") != null) contact.BusinessAddress = Get<string>(value, "postalAddress");
if (Get<string>(value, "businessAddressCity") != null) contact.BusinessAddressCity = Get<string>(value, "businessAddressCity");
if (Get<string>(value, "businessAddressPostalCode") != null) contact.BusinessAddressPostalCode = Get<string>(value, "businessAddressPostalCode");
if (Get<string>(value, "businessAddressPostOfficeBox") != null) contact.BusinessAddressPostOfficeBox = Get<string>(value, "businessAddressPostOfficeBox");
if (Get<string>(value, "businessAddressStateOrProvince") != null) contact.BusinessAddressState = Get<string>(value, "businessAddressStateOrProvince");
if (Get<string>(value, "language") != null) contact.Language = Get<string>(value, "language");
// Thumbnail
string photoData = Get<string>(value, "thumbnailPhoto");
if (photoData != null)
{
string path = null;
try
{
byte[] data = Convert.FromBase64String(photoData);
path = System.IO.Path.GetTempFileName();
Logger.Instance.Trace(this, "Contact image: {0}", path);
System.IO.File.WriteAllBytes(path, data);
contact.SetPicture(path);
}
catch (Exception) { }
finally
{
try
{
if (path != null)
System.IO.File.Delete(path);
}
catch (Exception) { }
}
}
// Resource flags
if (resourceType != 0)
{
contact.SetProperty(OutlookConstants.PR_DISPLAY_TYPE, 0);
contact.SetProperty(OutlookConstants.PR_DISPLAY_TYPE_EX, resourceType);
}
// Standard properties
SetItemStandard(contact, id, value, index);
contact.Save();
// Update the groups
AddItemToGroups(contact, id, value, index);
}
}
private void CreateGroup(string id, Dictionary<string, object> value, ChunkIndex index)
{
if (!_feature.CreateGroups)
return;
using (IDistributionList group = Contacts.Create<IDistributionList>())
{
Logger.Instance.Debug(this, "Creating group: {0}", id);
group.DLName = Get<string>(value, "displayName");
if (Get<string>(value, "smtpAddress") != null)
{
group.SMTPAddress = Get<string>(value, "smtpAddress");
}
SetItemStandard(group, id, value, index);
group.Save();
if (_feature.GroupMembers)
{
ArrayList members = Get<ArrayList>(value, "members");
if (members != null)
{
foreach (string memberId in members)
{
using (IItem item = FindItemById(memberId))
{
Logger.Instance.Debug(this, "Finding member {0} of {1}: {2}", memberId, id, item?.EntryId);
if (item != null)
AddGroupMember(group, item);
}
}
}
group.Save();
}
AddItemToGroups(group, id, value, index);
}
}
private IItem FindItemById(string id)
{
ISearch<IItem> search = Contacts.Search<IItem>();
search.AddField(PROP_GAB_ID, true).SetOperation(SearchOperation.Equal, id);
return search.SearchOne();
}
private void SetItemStandard(IItem item, string id, Dictionary<string, object> value, ChunkIndex index)
{
// Set the chunk data
item.GetUserProperty<int>(PROP_SEQUENCE, true).Value = index.numberOfChunks;
item.GetUserProperty<int>(PROP_CHUNK, true).Value = index.chunk;
item.GetUserProperty<string>(PROP_GAB_ID, true).Value = id;
}
private void AddGroupMember(IDistributionList group, IItem item)
{
if (!_feature.GroupMembersAdd)
return;
if (item is IDistributionList)
{
if (!_feature.NestedGroups)
return;
}
group.AddMember(item);
}
private void AddItemToGroups(IItem item, string id, Dictionary<string, object> value, ChunkIndex index)
{
if (!_feature.GroupMembers)
return;
// Find the groups
if (Get<ArrayList>(value, "memberOf") != null)
{
ArrayList members = Get<ArrayList>(value, "memberOf");
foreach (string memberOf in members)
{
using (IItem groupItem = FindItemById(memberOf))
{
Logger.Instance.Debug(this, "Finding group {0} for {1}: {2}", memberOf, id, groupItem?.EntryId);
if (groupItem is IDistributionList)
{
AddGroupMember((IDistributionList)groupItem, item);
groupItem.Save();
}
}
}
}
}
#endregion
#region Removal
public void Remove()
{
if (_contacts != null)
{
_contacts.Delete();
}
CleanupContactsObject();
}
private void CleanupContactsObject()
{
if (_contacts != null)
{
if (_contactsDisposer != null)
_contactsDisposer(_contacts);
_contacts.Dispose();
_contacts = null;
}
}
#endregion
}
}

View File

@ -0,0 +1,93 @@
/// 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 Acacia.Stubs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Acacia.Features.GAB
{
public class GABInfo
{
private const string ID = "GAB=";
public readonly string Domain;
public GABInfo(string domain)
{
this.Domain = domain;
}
/// <summary>
/// Retrieves the GAB info for the folder.
/// </summary>
/// <param name="folder">The folder</param>
/// <param name="forDomain">The domain name. If this is not null, and a GAB info is not found, it is created</param>
/// <returns></returns>
public static GABInfo Get(IFolder folder, string forDomain = null)
{
GABInfo gab = GetExisting(folder);
if (gab == null && forDomain != null)
gab = new GABInfo(forDomain);
return gab;
}
private static GABInfo GetExisting(IFolder folder)
{
string subject = null;
try
{
subject = (string)folder.GetProperty(OutlookConstants.PR_SUBJECT);
}
catch (System.Exception) { }
if (string.IsNullOrEmpty(subject))
return null;
string[] parts = subject.Split(';');
if (parts.Length < 1 || !parts[0].StartsWith(ID))
return null;
string domain = parts[0].Substring(ID.Length);
GABInfo gab = new GABInfo(domain);
return gab;
}
public override string ToString()
{
return "GAB(" + Domain + ")";
}
public bool IsForDomain(string domain)
{
return this.Domain == domain;
}
public void Store(IFolder folder)
{
string s = Serialize();
folder.SetProperty(OutlookConstants.PR_SUBJECT, s);
}
private string Serialize()
{
return ID + Domain;
}
}
}

View File

@ -0,0 +1,58 @@
namespace Acacia.Features.GAB
{
partial class GABSettings
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(GABSettings));
this.buttonGABResync = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// buttonGABResync
//
resources.ApplyResources(this.buttonGABResync, "buttonGABResync");
this.buttonGABResync.Name = "buttonGABResync";
this.buttonGABResync.UseVisualStyleBackColor = true;
this.buttonGABResync.Click += new System.EventHandler(this.buttonGABResync_Click);
//
// GABSettings
//
resources.ApplyResources(this, "$this");
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = System.Drawing.SystemColors.Window;
this.Controls.Add(this.buttonGABResync);
this.Name = "GABSettings";
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Button buttonGABResync;
}
}

View File

@ -0,0 +1,57 @@
/// 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.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Acacia.UI;
namespace Acacia.Features.GAB
{
public partial class GABSettings : FeatureSettings
{
private readonly FeatureGAB _feature;
public override Feature Feature
{
get
{
return _feature;
}
}
public GABSettings(FeatureGAB feature = null)
{
this._feature = feature;
InitializeComponent();
}
private void buttonGABResync_Click(object sender, EventArgs e)
{
// Allow null feature for designer
if (_feature != null)
{
_feature.FullResync();
}
}
}
}

View File

@ -0,0 +1,171 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="mscorlib" name="mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="buttonGABResync.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="buttonGABResync.Location" type="System.Drawing.Point, System.Drawing">
<value>3, 3</value>
</data>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="buttonGABResync.Padding" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>8, 0, 8, 0</value>
</data>
<data name="buttonGABResync.Size" type="System.Drawing.Size, System.Drawing">
<value>210, 23</value>
</data>
<data name="buttonGABResync.TabIndex" type="System.Int32, mscorlib">
<value>0</value>
</data>
<data name="buttonGABResync.Text" xml:space="preserve">
<value>Resynchronise Global Address Books</value>
</data>
<data name="&gt;&gt;buttonGABResync.Name" xml:space="preserve">
<value>buttonGABResync</value>
</data>
<data name="&gt;&gt;buttonGABResync.Type" xml:space="preserve">
<value>System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;buttonGABResync.Parent" xml:space="preserve">
<value>$this</value>
</data>
<data name="&gt;&gt;buttonGABResync.ZOrder" xml:space="preserve">
<value>0</value>
</data>
<metadata name="$this.Localizable" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<data name="$this.AutoScaleDimensions" type="System.Drawing.SizeF, System.Drawing">
<value>6, 13</value>
</data>
<data name="$this.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="$this.Size" type="System.Drawing.Size, System.Drawing">
<value>216, 31</value>
</data>
<data name="&gt;&gt;$this.Name" xml:space="preserve">
<value>GABSettings</value>
</data>
<data name="&gt;&gt;$this.Type" xml:space="preserve">
<value>Acacia.UI.FeatureSettings, ZPush, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null</value>
</data>
</root>

View File

@ -0,0 +1,292 @@
/// 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 Acacia.Stubs;
using Acacia.Stubs.OutlookWrappers;
using Acacia.Utils;
using Acacia.ZPush;
using Microsoft.Office.Interop.Outlook;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using static Acacia.DebugOptions;
namespace Acacia.Features.Notes
{
[AcaciaOption("Provides the possibility to synchronise Notes to and from a Z-Push server.")]
public class FeatureNotes : Feature
{
public FeatureNotes()
{
}
public override void Startup()
{
Watcher.WatchFolder(new FolderRegistrationTyped(this, ItemType.NoteItem),
OnNotesFolderDiscovered, OnNotesFolderChanged);
}
#region Debug options
[AcaciaOption("Disables the patching of Notes folders types. Without this, Outlook will not recognise " +
"these folders as being Notes folders, and contents will not be synchronised.")]
public bool PatchFolders
{
get { return GetOption(OPTION_PATCH_FOLDERS); }
set { SetOption(OPTION_PATCH_FOLDERS, value); }
}
private static readonly BoolOption OPTION_PATCH_FOLDERS = new BoolOption("PatchFolders", true);
[AcaciaOption("Disables the patching of Note item types. Without this, Outlook will not recognise " +
"these items as being Notes, and they may appear in unusual states.")]
public bool PatchItems
{
get { return GetOption(OPTION_PATCH_ITEMS); }
set { SetOption(OPTION_PATCH_ITEMS, value); }
}
private static readonly BoolOption OPTION_PATCH_ITEMS = new BoolOption("PatchItems", true);
#endregion
private void OnNotesFolderDiscovered(IFolder folder)
{
Logger.Instance.Debug(this, "NOTES FOLDER: {0}", folder);
// Always watch the folder. Any notes being synced in indicate the server supports notes,
// and otherwise there's no harm done.
Watcher.WatchItems<IItem>(folder, PatchNote, true);
// Patch the folder if needed
PatchIfConfirmed(folder);
}
private void OnNotesFolderChanged(IFolder folder)
{
Logger.Instance.Debug(this, "NOTES FOLDER CHANGED: {0}, type={1}", folder, folder.GetProperty(OutlookConstants.PR_EAS_SYNCTYPE));
// Outlook sometimes changes the type back. Patch again if needed.
PatchIfConfirmed(folder);
}
private bool IsNotesFolder(OutlookConstants.SyncType type)
{
return type == OutlookConstants.SyncType.Note || type == OutlookConstants.SyncType.UserNote;
}
private void PatchIfConfirmed(IFolder folder)
{
// Only patch if on a ZPush server that supports notes. Store the folder as entryId, there have been some
// issues with the folder object being disposed in the past
string folderId = folder.EntryId;
ZPushAccount zpush = Watcher.Accounts.GetAccount(folder);
if (zpush != null)
{
zpush.ConfirmedChanged += (z) =>
{
if (zpush.Confirmed == ZPushAccount.ConfirmationType.IsZPush &&
zpush.Capabilities.Has(Constants.ZPUSH_CAPABILITY_NOTES))
{
PatchFolder(folderId);
}
else if (zpush.Confirmed != ZPushAccount.ConfirmationType.Unknown)
{
// The server is either not a Z-Push server, or it does not support notes
// Restore any patched notes folder
UnpatchFolder(folderId);
}
};
}
}
private void PatchFolder(string folderId)
{
if (!PatchFolders)
return;
Logger.Instance.Trace(this, "PatchFolder: {0}", folderId);
try
{
using (IFolder folder = Mapping.GetFolderFromID(folderId))
{
if (folder == null)
return;
// Patch if needed
OutlookConstants.SyncType type = FolderUtils.GetFolderSyncType(folder);
Logger.Instance.Trace(this, "Notes folder type: {0}", type);
if (IsNotesFolder(type))
{
Logger.Instance.Debug(this, "Patching Notes folder type: {0}", type);
// Change to task folder
folder.SetProperties(new string[]
{
OutlookConstants.PR_NET_FOLDER_FLAGS,
OutlookConstants.PR_EAS_SYNCTYPE,
OutlookConstants.PR_EAS_SYNC1,
OutlookConstants.PR_EAS_SYNC2
}, new object[]
{
0, (int)OutlookConstants.SyncType.UserAppointment, true, true
});
if (type == OutlookConstants.SyncType.Note)
{
// Local notes, change name
PatchFolderName(folder);
}
}
}
}
finally
{
Logger.Instance.Trace(this, "PatchFolder done");
}
}
private void UnpatchFolder(string folderId)
{
if (!PatchFolders)
return;
Logger.Instance.Trace(this, "UnpatchFolder: {0}", folderId);
try
{
using (IFolder folder = Mapping.GetFolderFromID(folderId))
{
if (folder == null)
return;
// Unpatch if needed
OutlookConstants.SyncType type = FolderUtils.GetFolderSyncType(folder, true);
Logger.Instance.Trace(this, "Notes folder type: {0}", type);
// Unpatch only if the original type is a notes folder, but the current type isn't
if (IsNotesFolder(type) && !IsNotesFolder(FolderUtils.GetFolderSyncType(folder)))
{
Logger.Instance.Debug(this, "Unpatching Notes folder type: {0}", type);
// Change to original notes folder
folder.SetProperties(new string[]
{
OutlookConstants.PR_EAS_SYNCTYPE,
OutlookConstants.PR_EAS_SYNC1,
OutlookConstants.PR_EAS_SYNC2
}, new object[]
{
(int)type,
false,
false
});
if (type == OutlookConstants.SyncType.Note)
{
// Local notes, change name
UnpatchFolderName(folder);
}
}
}
}
finally
{
Logger.Instance.Trace(this, "PatchFolder done");
}
}
private void PatchNote(IItem item)
{
if (!PatchItems)
return;
Logger.Instance.Trace(this, "NOTE ITEM: Subject='{0}', Class={1}",
item.GetProperty(OutlookConstants.PR_SUBJECT),
item.GetProperty(OutlookConstants.PR_MESSAGE_CLASS));
try
{
if ((int)item.GetProperty(OutlookConstants.PR_ICON_INDEX) != 771)
{
Logger.Instance.Trace(this, "Patching item: {0}", item.EntryId);
// Patch standard properties
item.SetProperties(
new string[] { OutlookConstants.PR_MESSAGE_CLASS, OutlookConstants.PR_ICON_INDEX, OutlookConstants.PR_NOTE_COLOR },
new object[] { OutlookConstants.MESSAGE_CLASS_NOTES, 771, 3 }
);
// Set sizes if not set, they get crappy defaults
try
{
// This causes an exception if nothing is set
item.GetProperty(OutlookConstants.PR_NOTE_WIDTH);
}
catch (System.Exception)
{
Logger.Instance.Trace(this, "Setting default sizes");
item.SetProperty(OutlookConstants.PR_NOTE_WIDTH, 200);
item.SetProperty(OutlookConstants.PR_NOTE_HEIGHT, 166);
item.SetProperty(OutlookConstants.PR_NOTE_X, 80);
item.SetProperty(OutlookConstants.PR_NOTE_Y, 80);
}
item.Save();
}
}
finally
{
Logger.Instance.Trace(this, "PatchNote done");
}
}
private void PatchFolderName(IFolder folder)
{
// Remove parenthesised (this computer only) or localised equivalent
string oldName = folder.Name;
int open = oldName.IndexOf('(');
int close = oldName.IndexOf(')');
if (open >= 0 && close >= 0)
{
string newName = oldName.Substring(0, Math.Min(open, close)) + oldName.Substring(Math.Max(open, close) + 1);
newName = newName.Trim();
// Set the new name, and keep the old name in subject in case of a revert
folder.SetProperties(new string[]
{
OutlookConstants.PR_DISPLAY_NAME, OutlookConstants.PR_SUBJECT
}, new object[]
{
newName, oldName
});
}
}
private void UnpatchFolderName(IFolder folder)
{
try
{
string oldName = (string)folder.GetProperty(OutlookConstants.PR_SUBJECT);
// Parentheses are not allowed in names (even though they were there originally)
// Replace with square brackets.
oldName = oldName.Replace('(', '[');
oldName = oldName.Replace(')', ']');
folder.SetProperty(OutlookConstants.PR_DISPLAY_NAME, oldName);
}
catch(System.Exception e)
{
Logger.Instance.Warning(this, "Exception in UnpatchFolderName, leaving name: {0}", e);
}
}
}
}

View File

@ -0,0 +1,241 @@
/// 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 Acacia.UI;
using Acacia.UI.Outlook;
using Acacia.Utils;
using Acacia.ZPush;
using Acacia.ZPush.Connect;
using Microsoft.Office.Interop.Outlook;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Acacia.Features.OutOfOffice
{
[AcaciaOption("Provides a user interface to modify Out-of-Office settings for ActiveSync accounts.")]
public class FeatureOutOfOffice
:
Feature, FeatureWithRibbon
{
private RibbonToggleButton _button;
public FeatureOutOfOffice()
{
}
public override void Startup()
{
_button = RegisterToggleButton(this, "OOF", true, ShowDialog, ZPushBehaviour.Disable);
Watcher.ZPushAccountChange += Watcher_ZPushAccountChange;
}
private static bool IsOOFEnabled(ActiveSync.SettingsOOF settings)
{
if (settings == null)
return false;
return settings.State != ActiveSync.OOFState.Disabled;
}
private void Watcher_ZPushAccountChange(ZPushAccount account)
{
if (_button != null)
{
if (account == null)
_button.IsPressed = false;
else
_button.IsPressed = IsOOFEnabled(account.GetFeatureData<ActiveSync.SettingsOOF>(this, "OOF"));
}
}
private void StoreOOFSettings(ZPushAccount account, ActiveSync.SettingsOOF settings)
{
account.SetFeatureData(this, "OOF", settings);
if (_button != null)
_button.IsPressed = IsOOFEnabled(settings);
}
private void ShowDialog()
{
ZPushAccount account = Watcher.CurrentZPushAccount();
if (account != null)
{
try
{
// Fetch the current status
ActiveSync.SettingsOOF settings;
try
{
settings = ProgressDialog.Execute("OOFGet",
(ct) =>
{
using (ZPushConnection con = new ZPushConnection(account, ct))
return con.Execute(new ActiveSync.SettingsOOFGet());
}
);
}
catch (System.Exception e)
{
Logger.Instance.Warning(this, "Exception getting OOF state: {0}", e);
if (MessageBox.Show(
Properties.Resources.OOFGet_Failed,
Properties.Resources.OOFGet_Title,
MessageBoxButtons.OKCancel,
MessageBoxIcon.Error
) != DialogResult.OK)
{
return;
}
else
{
// Initialise default settings
settings = new ActiveSync.SettingsOOF();
settings.Message = new ActiveSync.OOFMessage[3];
}
}
// Store them for later use
StoreOOFSettings(account, settings);
// Show the dialog
ShowOOFDialog(account, settings);
}
catch(System.Exception e)
{
Logger.Instance.Warning(this, "Exception: {0}", e);
}
}
}
private void ShowOOFDialog(ZPushAccount account, ActiveSync.SettingsOOF settings)
{
// Show dialog
if (new OutOfOfficeDialog(account, settings).ShowDialog() != DialogResult.OK)
return;
try
{
// Store settings
ActiveSync.SettingsOOF actualSettings = ProgressDialog.Execute("OOFSet",
(ct) =>
{
using (ZPushConnection connection = new ZPushConnection(account, ct))
{
// Set the OOF state. This always seems to return ok, so we fetch the settings
// again, to see what happend
connection.Execute(new ActiveSync.SettingsOOFSet(settings));
// Fetch the OOF state
return connection.Execute(new ActiveSync.SettingsOOFGet());
}
}
);
// Store them for later use
StoreOOFSettings(account, actualSettings);
// Check what happened
string message;
MessageBoxIcon messageIcon;
if (settings.State == ActiveSync.OOFState.Disabled)
{
// Tried to disable.
if (actualSettings.State != ActiveSync.OOFState.Disabled)
{
// It's an error if its not actually disabled
message = Properties.Resources.OOFSet_DisableFailed;
messageIcon = MessageBoxIcon.Error;
}
else
{
// All good
message = Properties.Resources.OOFSet_Disabled;
messageIcon = MessageBoxIcon.Information;
}
}
else if (actualSettings.State == ActiveSync.OOFState.Disabled)
{
// It's an error if the state is set to disabled when we tried to enable
message = Properties.Resources.OOFSet_EnableFailed;
messageIcon = MessageBoxIcon.Error;
}
else
{
// All good
if (actualSettings.State == ActiveSync.OOFState.EnabledTimeBased)
{
message = string.Format(Properties.Resources.OOFSet_EnabledTimeBased,
actualSettings.From, actualSettings.Till);
}
else
{
message = Properties.Resources.OOFSet_Enabled;
}
messageIcon = MessageBoxIcon.Information;
// It's okay if the state is not the same, but it deserves a message
if (actualSettings.State != settings.State)
{
message = Properties.Resources.OOFSet_DifferentState + message;
messageIcon = MessageBoxIcon.Warning;
}
}
Logger.Instance.Debug(this, "OOF state updated: {0}, {1}", message, messageIcon);
MessageBox.Show(message,
Properties.Resources.OOFSet_Title,
MessageBoxButtons.OK,
messageIcon
);
}
catch (System.Exception e)
{
ErrorUtil.HandleErrorNew(this, "Exception in OOFSet", e,
Properties.Resources.OOFSet_Title, Properties.Resources.OOFSet_Failed);
}
}
/// <summary>
/// Invoked by AccountWatcher on start-up to notify of the oof status.
/// </summary>
public void OnOOFSettings(ZPushAccount account, ActiveSync.SettingsOOF oof)
{
// Store them for later use
StoreOOFSettings(account, oof);
// Show a message if OOF is enabled
if (oof.State != ActiveSync.OOFState.Disabled)
{
if (MessageBox.Show(
string.Format(Properties.Resources.OOFStartup_Message, account.SmtpAddress),
Properties.Resources.OOFStartup_Title,
MessageBoxButtons.YesNo,
MessageBoxIcon.Question
) == DialogResult.Yes)
{
ShowOOFDialog(account, oof);
}
}
}
}
}

View File

@ -0,0 +1,225 @@
namespace Acacia.Features.OutOfOffice
{
partial class OutOfOfficeDialog
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(OutOfOfficeDialog));
this.tableGlobal = new System.Windows.Forms.TableLayoutPanel();
this.chkEnable = new System.Windows.Forms.CheckBox();
this.tableDates = new System.Windows.Forms.TableLayoutPanel();
this.radioNoTime = new System.Windows.Forms.RadioButton();
this.radioTime = new System.Windows.Forms.RadioButton();
this.dateFrom = new System.Windows.Forms.DateTimePicker();
this.timeFrom = new System.Windows.Forms.DateTimePicker();
this.labelTill = new System.Windows.Forms.Label();
this.dateTill = new System.Windows.Forms.DateTimePicker();
this.timeTill = new System.Windows.Forms.DateTimePicker();
this.groupTextEntry = new System.Windows.Forms.GroupBox();
this.tableTextEntry = new System.Windows.Forms.TableLayoutPanel();
this.labelBody = new System.Windows.Forms.Label();
this.textBody = new System.Windows.Forms.TextBox();
this.flowButtons = new System.Windows.Forms.FlowLayoutPanel();
this.btnCancel = new System.Windows.Forms.Button();
this.btnSave = new System.Windows.Forms.Button();
this.tableGlobal.SuspendLayout();
this.tableDates.SuspendLayout();
this.groupTextEntry.SuspendLayout();
this.tableTextEntry.SuspendLayout();
this.flowButtons.SuspendLayout();
this.SuspendLayout();
//
// tableGlobal
//
resources.ApplyResources(this.tableGlobal, "tableGlobal");
this.tableGlobal.Controls.Add(this.chkEnable, 0, 0);
this.tableGlobal.Controls.Add(this.tableDates, 0, 1);
this.tableGlobal.Controls.Add(this.groupTextEntry, 0, 2);
this.tableGlobal.Controls.Add(this.flowButtons, 0, 3);
this.tableGlobal.Name = "tableGlobal";
//
// chkEnable
//
resources.ApplyResources(this.chkEnable, "chkEnable");
this.chkEnable.Name = "chkEnable";
this.chkEnable.UseVisualStyleBackColor = true;
this.chkEnable.CheckedChanged += new System.EventHandler(this.chkEnable_CheckedChanged);
//
// tableDates
//
resources.ApplyResources(this.tableDates, "tableDates");
this.tableDates.Controls.Add(this.radioNoTime, 0, 0);
this.tableDates.Controls.Add(this.radioTime, 0, 1);
this.tableDates.Controls.Add(this.dateFrom, 1, 1);
this.tableDates.Controls.Add(this.timeFrom, 2, 1);
this.tableDates.Controls.Add(this.labelTill, 0, 2);
this.tableDates.Controls.Add(this.dateTill, 1, 2);
this.tableDates.Controls.Add(this.timeTill, 2, 2);
this.tableDates.Name = "tableDates";
//
// radioNoTime
//
resources.ApplyResources(this.radioNoTime, "radioNoTime");
this.radioNoTime.Checked = true;
this.tableDates.SetColumnSpan(this.radioNoTime, 3);
this.radioNoTime.Name = "radioNoTime";
this.radioNoTime.TabStop = true;
this.radioNoTime.UseVisualStyleBackColor = true;
//
// radioTime
//
resources.ApplyResources(this.radioTime, "radioTime");
this.radioTime.Name = "radioTime";
this.radioTime.UseVisualStyleBackColor = true;
this.radioTime.CheckedChanged += new System.EventHandler(this.radioTime_CheckedChanged);
//
// dateFrom
//
resources.ApplyResources(this.dateFrom, "dateFrom");
this.dateFrom.Name = "dateFrom";
this.dateFrom.ValueChanged += new System.EventHandler(this.dateFrom_ValueChanged);
//
// timeFrom
//
resources.ApplyResources(this.timeFrom, "timeFrom");
this.timeFrom.Format = System.Windows.Forms.DateTimePickerFormat.Custom;
this.timeFrom.Name = "timeFrom";
this.timeFrom.ShowUpDown = true;
this.timeFrom.ValueChanged += new System.EventHandler(this.timeFrom_ValueChanged);
//
// labelTill
//
resources.ApplyResources(this.labelTill, "labelTill");
this.labelTill.Name = "labelTill";
//
// dateTill
//
resources.ApplyResources(this.dateTill, "dateTill");
this.dateTill.Name = "dateTill";
this.dateTill.ValueChanged += new System.EventHandler(this.dateTill_ValueChanged);
//
// timeTill
//
resources.ApplyResources(this.timeTill, "timeTill");
this.timeTill.Format = System.Windows.Forms.DateTimePickerFormat.Custom;
this.timeTill.Name = "timeTill";
this.timeTill.ShowUpDown = true;
//
// groupTextEntry
//
resources.ApplyResources(this.groupTextEntry, "groupTextEntry");
this.groupTextEntry.Controls.Add(this.tableTextEntry);
this.groupTextEntry.Name = "groupTextEntry";
this.groupTextEntry.TabStop = false;
//
// tableTextEntry
//
resources.ApplyResources(this.tableTextEntry, "tableTextEntry");
this.tableTextEntry.Controls.Add(this.labelBody, 0, 0);
this.tableTextEntry.Controls.Add(this.textBody, 0, 1);
this.tableTextEntry.Name = "tableTextEntry";
//
// labelBody
//
resources.ApplyResources(this.labelBody, "labelBody");
this.labelBody.Name = "labelBody";
//
// textBody
//
this.textBody.AcceptsReturn = true;
resources.ApplyResources(this.textBody, "textBody");
this.textBody.Name = "textBody";
//
// flowButtons
//
resources.ApplyResources(this.flowButtons, "flowButtons");
this.flowButtons.Controls.Add(this.btnCancel);
this.flowButtons.Controls.Add(this.btnSave);
this.flowButtons.Name = "flowButtons";
//
// btnCancel
//
resources.ApplyResources(this.btnCancel, "btnCancel");
this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.btnCancel.Name = "btnCancel";
this.btnCancel.UseVisualStyleBackColor = true;
//
// btnSave
//
resources.ApplyResources(this.btnSave, "btnSave");
this.btnSave.DialogResult = System.Windows.Forms.DialogResult.OK;
this.btnSave.Name = "btnSave";
this.btnSave.UseVisualStyleBackColor = true;
//
// OutOfOfficeDialog
//
this.AcceptButton = this.btnSave;
resources.ApplyResources(this, "$this");
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.CancelButton = this.btnCancel;
this.Controls.Add(this.tableGlobal);
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "OutOfOfficeDialog";
this.ShowInTaskbar = false;
this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Show;
this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.OutOfOfficeDialog_FormClosed);
this.tableGlobal.ResumeLayout(false);
this.tableGlobal.PerformLayout();
this.tableDates.ResumeLayout(false);
this.tableDates.PerformLayout();
this.groupTextEntry.ResumeLayout(false);
this.groupTextEntry.PerformLayout();
this.tableTextEntry.ResumeLayout(false);
this.tableTextEntry.PerformLayout();
this.flowButtons.ResumeLayout(false);
this.flowButtons.PerformLayout();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.TableLayoutPanel tableGlobal;
private System.Windows.Forms.CheckBox chkEnable;
private System.Windows.Forms.RadioButton radioNoTime;
private System.Windows.Forms.GroupBox groupTextEntry;
private System.Windows.Forms.FlowLayoutPanel flowButtons;
private System.Windows.Forms.Button btnCancel;
private System.Windows.Forms.Button btnSave;
private System.Windows.Forms.TableLayoutPanel tableDates;
private System.Windows.Forms.RadioButton radioTime;
private System.Windows.Forms.DateTimePicker dateFrom;
private System.Windows.Forms.DateTimePicker dateTill;
private System.Windows.Forms.TableLayoutPanel tableTextEntry;
private System.Windows.Forms.Label labelBody;
private System.Windows.Forms.TextBox textBody;
private System.Windows.Forms.DateTimePicker timeFrom;
private System.Windows.Forms.DateTimePicker timeTill;
private System.Windows.Forms.Label labelTill;
}
}

View File

@ -0,0 +1,192 @@
/// 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 Acacia.UI;
using Acacia.Utils;
using Acacia.ZPush;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Acacia.Features.OutOfOffice
{
public partial class OutOfOfficeDialog : KopanoDialog
{
private ActiveSync.SettingsOOF _settings;
private readonly bool haveTimes;
public OutOfOfficeDialog(ZPushAccount account, ActiveSync.SettingsOOF settings)
{
this._settings = settings;
InitializeComponent();
// Add the email address to the title
Text = string.Format(Text, account.SmtpAddress);
// Set the time formats
timeFrom.CustomFormat = CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern;
timeTill.CustomFormat = CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern;
// Patch the position of the until label, to align
// with the from text
using (Graphics graphics = radioTime.CreateGraphics())
{
Size size = RadioButtonRenderer.GetGlyphSize(graphics, System.Windows.Forms.VisualStyles.RadioButtonState.CheckedNormal);
Padding padding = labelTill.Margin;
padding.Left = radioTime.Margin.Left + size.Width + 2;
labelTill.Margin = padding;
}
// Enable controls
chkEnable_CheckedChanged(chkEnable, null);
radioTime_CheckedChanged(radioTime, null);
// Hide time options, only if it is known that these are not supported
haveTimes = _settings.SupportsTimes != false;
if (!haveTimes)
{
tableDates.Visible = false;
}
// Load settings
switch(settings.State)
{
case ActiveSync.OOFState.Disabled:
chkEnable.Checked = false;
break;
case ActiveSync.OOFState.Enabled:
chkEnable.Checked = true;
radioNoTime.Checked = true;
break;
case ActiveSync.OOFState.EnabledTimeBased:
chkEnable.Checked = true;
radioTime.Checked = true;
dateFrom.Value = settings.From.Value;
timeFrom.Value = settings.From.Value;
dateTill.Value = settings.Till.Value;
timeTill.Value = settings.Till.Value;
break;
}
textBody.Text = settings.Message[(int)ActiveSync.OOFTarget.Internal]?.Message;
// Set up limits
SetTillTimeLimit();
}
private void chkEnable_CheckedChanged(object sender, EventArgs e)
{
tableDates.Enabled = chkEnable.Checked;
groupTextEntry.Enabled = chkEnable.Checked;
}
private void radioTime_CheckedChanged(object sender, EventArgs e)
{
dateFrom.Enabled = timeFrom.Enabled = radioTime.Checked;
dateTill.Enabled = timeTill.Enabled = radioTime.Checked;
}
private void OutOfOfficeDialog_FormClosed(object sender, FormClosedEventArgs e)
{
// Save the settings
_settings.From = null;
_settings.Till = null;
if (chkEnable.Checked)
{
if (radioNoTime.Checked || !haveTimes)
{
_settings.State = ActiveSync.OOFState.Enabled;
}
else
{
_settings.State = ActiveSync.OOFState.EnabledTimeBased;
_settings.From = GetDateTime(dateFrom, timeFrom);
_settings.Till = GetDateTime(dateTill, timeTill);
}
}
else
{
_settings.State = ActiveSync.OOFState.Disabled;
}
// Always set the message, so it's stored
string message = textBody.Text;
for (int i = 0; i < 3; ++i)
{
_settings.Message[i] = new ActiveSync.OOFMessage();
_settings.Message[i].Message = message;
}
}
private DateTime GetDateTime(DateTimePicker dateControl, DateTimePicker timeControl)
{
DateTime date = dateControl.Value;
DateTime time = timeControl.Value;
DateTime combined = new DateTime(date.Year, date.Month, date.Day);
combined = combined.Add(time.TimeOfDay);
return combined;
}
#region Date/Time checking
private void dateFrom_ValueChanged(object sender, EventArgs e)
{
SetTillTimeLimit();
}
private void timeFrom_ValueChanged(object sender, EventArgs e)
{
SetTillTimeLimit();
}
private void dateTill_ValueChanged(object sender, EventArgs e)
{
SetTillTimeLimit();
}
private void timeTill_ValueChanged(object sender, EventArgs e)
{
SetTillTimeLimit();
}
private void SetTillTimeLimit()
{
// Don't allow setting till to before from, or before now
dateTill.MinDate = new DateTime(Math.Max(dateFrom.Value.Ticks, DateTime.Today.Ticks));
if (dateTill.Value.Date == dateFrom.Value.Date)
{
timeTill.MinDate = timeFrom.Value;
}
else
{
timeTill.MinDate = DateTimePicker.MinimumDateTime;
}
}
#endregion
}
}

View File

@ -0,0 +1,690 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="tableGlobal.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
<value>Top, Bottom, Left, Right</value>
</data>
<assembly alias="mscorlib" name="mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="tableGlobal.ColumnCount" type="System.Int32, mscorlib">
<value>1</value>
</data>
<data name="chkEnable.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="chkEnable.Location" type="System.Drawing.Point, System.Drawing">
<value>3, 2</value>
</data>
<data name="chkEnable.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>3, 2, 3, 2</value>
</data>
<data name="chkEnable.Size" type="System.Drawing.Size, System.Drawing">
<value>262, 21</value>
</data>
<data name="chkEnable.TabIndex" type="System.Int32, mscorlib">
<value>0</value>
</data>
<data name="chkEnable.Text" xml:space="preserve">
<value>Enable out-of-office auto-responding</value>
</data>
<data name="&gt;&gt;chkEnable.Name" xml:space="preserve">
<value>chkEnable</value>
</data>
<data name="&gt;&gt;chkEnable.Type" xml:space="preserve">
<value>System.Windows.Forms.CheckBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;chkEnable.Parent" xml:space="preserve">
<value>tableGlobal</value>
</data>
<data name="&gt;&gt;chkEnable.ZOrder" xml:space="preserve">
<value>0</value>
</data>
<data name="tableDates.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
<value>Top, Bottom, Left, Right</value>
</data>
<data name="tableDates.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="tableDates.AutoSizeMode" type="System.Windows.Forms.AutoSizeMode, System.Windows.Forms">
<value>GrowAndShrink</value>
</data>
<data name="tableDates.ColumnCount" type="System.Int32, mscorlib">
<value>3</value>
</data>
<data name="radioNoTime.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
<value>Top, Bottom, Left</value>
</data>
<data name="radioNoTime.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="radioNoTime.Location" type="System.Drawing.Point, System.Drawing">
<value>24, 2</value>
</data>
<data name="radioNoTime.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>24, 2, 3, 2</value>
</data>
<data name="radioNoTime.Size" type="System.Drawing.Size, System.Drawing">
<value>143, 21</value>
</data>
<data name="radioNoTime.TabIndex" type="System.Int32, mscorlib">
<value>1</value>
</data>
<data name="radioNoTime.Text" xml:space="preserve">
<value>until further notice</value>
</data>
<data name="&gt;&gt;radioNoTime.Name" xml:space="preserve">
<value>radioNoTime</value>
</data>
<data name="&gt;&gt;radioNoTime.Type" xml:space="preserve">
<value>System.Windows.Forms.RadioButton, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;radioNoTime.Parent" xml:space="preserve">
<value>tableDates</value>
</data>
<data name="&gt;&gt;radioNoTime.ZOrder" xml:space="preserve">
<value>0</value>
</data>
<data name="radioTime.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
<value>Top, Bottom, Left</value>
</data>
<data name="radioTime.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="radioTime.Location" type="System.Drawing.Point, System.Drawing">
<value>24, 27</value>
</data>
<data name="radioTime.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>24, 2, 3, 2</value>
</data>
<data name="radioTime.Size" type="System.Drawing.Size, System.Drawing">
<value>57, 22</value>
</data>
<data name="radioTime.TabIndex" type="System.Int32, mscorlib">
<value>2</value>
</data>
<data name="radioTime.Text" xml:space="preserve">
<value>from</value>
</data>
<data name="&gt;&gt;radioTime.Name" xml:space="preserve">
<value>radioTime</value>
</data>
<data name="&gt;&gt;radioTime.Type" xml:space="preserve">
<value>System.Windows.Forms.RadioButton, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;radioTime.Parent" xml:space="preserve">
<value>tableDates</value>
</data>
<data name="&gt;&gt;radioTime.ZOrder" xml:space="preserve">
<value>1</value>
</data>
<data name="dateFrom.Location" type="System.Drawing.Point, System.Drawing">
<value>87, 27</value>
</data>
<data name="dateFrom.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>3, 2, 3, 2</value>
</data>
<data name="dateFrom.Size" type="System.Drawing.Size, System.Drawing">
<value>169, 22</value>
</data>
<data name="dateFrom.TabIndex" type="System.Int32, mscorlib">
<value>3</value>
</data>
<data name="&gt;&gt;dateFrom.Name" xml:space="preserve">
<value>dateFrom</value>
</data>
<data name="&gt;&gt;dateFrom.Type" xml:space="preserve">
<value>System.Windows.Forms.DateTimePicker, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;dateFrom.Parent" xml:space="preserve">
<value>tableDates</value>
</data>
<data name="&gt;&gt;dateFrom.ZOrder" xml:space="preserve">
<value>2</value>
</data>
<data name="timeFrom.CustomFormat" xml:space="preserve">
<value>HH:mm</value>
</data>
<data name="timeFrom.Location" type="System.Drawing.Point, System.Drawing">
<value>262, 27</value>
</data>
<data name="timeFrom.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>3, 2, 3, 2</value>
</data>
<data name="timeFrom.Size" type="System.Drawing.Size, System.Drawing">
<value>87, 22</value>
</data>
<data name="timeFrom.TabIndex" type="System.Int32, mscorlib">
<value>4</value>
</data>
<data name="&gt;&gt;timeFrom.Name" xml:space="preserve">
<value>timeFrom</value>
</data>
<data name="&gt;&gt;timeFrom.Type" xml:space="preserve">
<value>System.Windows.Forms.DateTimePicker, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;timeFrom.Parent" xml:space="preserve">
<value>tableDates</value>
</data>
<data name="&gt;&gt;timeFrom.ZOrder" xml:space="preserve">
<value>3</value>
</data>
<data name="labelTill.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
<value>Top, Bottom, Left, Right</value>
</data>
<data name="labelTill.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="labelTill.Location" type="System.Drawing.Point, System.Drawing">
<value>44, 51</value>
</data>
<data name="labelTill.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>44, 0, 3, 0</value>
</data>
<data name="labelTill.Size" type="System.Drawing.Size, System.Drawing">
<value>37, 26</value>
</data>
<data name="labelTill.TabIndex" type="System.Int32, mscorlib">
<value>7</value>
</data>
<data name="labelTill.Text" xml:space="preserve">
<value>until</value>
</data>
<data name="labelTill.TextAlign" type="System.Drawing.ContentAlignment, System.Drawing">
<value>MiddleLeft</value>
</data>
<data name="&gt;&gt;labelTill.Name" xml:space="preserve">
<value>labelTill</value>
</data>
<data name="&gt;&gt;labelTill.Type" xml:space="preserve">
<value>System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;labelTill.Parent" xml:space="preserve">
<value>tableDates</value>
</data>
<data name="&gt;&gt;labelTill.ZOrder" xml:space="preserve">
<value>4</value>
</data>
<data name="dateTill.Location" type="System.Drawing.Point, System.Drawing">
<value>87, 53</value>
</data>
<data name="dateTill.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>3, 2, 3, 2</value>
</data>
<data name="dateTill.Size" type="System.Drawing.Size, System.Drawing">
<value>169, 22</value>
</data>
<data name="dateTill.TabIndex" type="System.Int32, mscorlib">
<value>5</value>
</data>
<data name="&gt;&gt;dateTill.Name" xml:space="preserve">
<value>dateTill</value>
</data>
<data name="&gt;&gt;dateTill.Type" xml:space="preserve">
<value>System.Windows.Forms.DateTimePicker, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;dateTill.Parent" xml:space="preserve">
<value>tableDates</value>
</data>
<data name="&gt;&gt;dateTill.ZOrder" xml:space="preserve">
<value>5</value>
</data>
<data name="timeTill.CustomFormat" xml:space="preserve">
<value>HH:mm</value>
</data>
<data name="timeTill.Location" type="System.Drawing.Point, System.Drawing">
<value>262, 53</value>
</data>
<data name="timeTill.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>3, 2, 3, 2</value>
</data>
<data name="timeTill.Size" type="System.Drawing.Size, System.Drawing">
<value>87, 22</value>
</data>
<data name="timeTill.TabIndex" type="System.Int32, mscorlib">
<value>6</value>
</data>
<data name="&gt;&gt;timeTill.Name" xml:space="preserve">
<value>timeTill</value>
</data>
<data name="&gt;&gt;timeTill.Type" xml:space="preserve">
<value>System.Windows.Forms.DateTimePicker, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;timeTill.Parent" xml:space="preserve">
<value>tableDates</value>
</data>
<data name="&gt;&gt;timeTill.ZOrder" xml:space="preserve">
<value>6</value>
</data>
<data name="tableDates.Location" type="System.Drawing.Point, System.Drawing">
<value>0, 25</value>
</data>
<data name="tableDates.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>0, 0, 0, 0</value>
</data>
<data name="tableDates.RowCount" type="System.Int32, mscorlib">
<value>3</value>
</data>
<data name="tableDates.Size" type="System.Drawing.Size, System.Drawing">
<value>431, 77</value>
</data>
<data name="tableDates.TabIndex" type="System.Int32, mscorlib">
<value>1</value>
</data>
<data name="&gt;&gt;tableDates.Name" xml:space="preserve">
<value>tableDates</value>
</data>
<data name="&gt;&gt;tableDates.Type" xml:space="preserve">
<value>System.Windows.Forms.TableLayoutPanel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;tableDates.Parent" xml:space="preserve">
<value>tableGlobal</value>
</data>
<data name="&gt;&gt;tableDates.ZOrder" xml:space="preserve">
<value>1</value>
</data>
<data name="tableDates.LayoutSettings" type="System.Windows.Forms.TableLayoutSettings, System.Windows.Forms">
<value>&lt;?xml version="1.0" encoding="utf-16"?&gt;&lt;TableLayoutSettings&gt;&lt;Controls&gt;&lt;Control Name="radioNoTime" Row="0" RowSpan="1" Column="0" ColumnSpan="3" /&gt;&lt;Control Name="radioTime" Row="1" RowSpan="1" Column="0" ColumnSpan="1" /&gt;&lt;Control Name="dateFrom" Row="1" RowSpan="1" Column="1" ColumnSpan="1" /&gt;&lt;Control Name="timeFrom" Row="1" RowSpan="1" Column="2" ColumnSpan="1" /&gt;&lt;Control Name="labelTill" Row="2" RowSpan="1" Column="0" ColumnSpan="1" /&gt;&lt;Control Name="dateTill" Row="2" RowSpan="1" Column="1" ColumnSpan="1" /&gt;&lt;Control Name="timeTill" Row="2" RowSpan="1" Column="2" ColumnSpan="1" /&gt;&lt;/Controls&gt;&lt;Columns Styles="AutoSize,0,AutoSize,0,AutoSize,0,Absolute,20" /&gt;&lt;Rows Styles="AutoSize,0,AutoSize,0,AutoSize,0" /&gt;&lt;/TableLayoutSettings&gt;</value>
</data>
<data name="groupTextEntry.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
<value>Top, Bottom, Left, Right</value>
</data>
<data name="groupTextEntry.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="tableTextEntry.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
<value>Top, Bottom, Left, Right</value>
</data>
<data name="tableTextEntry.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="tableTextEntry.AutoSizeMode" type="System.Windows.Forms.AutoSizeMode, System.Windows.Forms">
<value>GrowAndShrink</value>
</data>
<data name="tableTextEntry.ColumnCount" type="System.Int32, mscorlib">
<value>1</value>
</data>
<data name="labelBody.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="labelBody.Location" type="System.Drawing.Point, System.Drawing">
<value>3, 6</value>
</data>
<data name="labelBody.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>3, 6, 3, 0</value>
</data>
<data name="labelBody.Size" type="System.Drawing.Size, System.Drawing">
<value>377, 17</value>
</data>
<data name="labelBody.TabIndex" type="System.Int32, mscorlib">
<value>2</value>
</data>
<data name="labelBody.Text" xml:space="preserve">
<value>AutoReply only once to each sender with the following text:</value>
</data>
<data name="&gt;&gt;labelBody.Name" xml:space="preserve">
<value>labelBody</value>
</data>
<data name="&gt;&gt;labelBody.Type" xml:space="preserve">
<value>System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;labelBody.Parent" xml:space="preserve">
<value>tableTextEntry</value>
</data>
<data name="&gt;&gt;labelBody.ZOrder" xml:space="preserve">
<value>0</value>
</data>
<data name="textBody.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
<value>Top, Bottom, Left, Right</value>
</data>
<data name="textBody.Location" type="System.Drawing.Point, System.Drawing">
<value>3, 25</value>
</data>
<data name="textBody.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>3, 2, 3, 2</value>
</data>
<data name="textBody.Multiline" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="textBody.Size" type="System.Drawing.Size, System.Drawing">
<value>411, 168</value>
</data>
<data name="textBody.TabIndex" type="System.Int32, mscorlib">
<value>8</value>
</data>
<data name="&gt;&gt;textBody.Name" xml:space="preserve">
<value>textBody</value>
</data>
<data name="&gt;&gt;textBody.Type" xml:space="preserve">
<value>System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;textBody.Parent" xml:space="preserve">
<value>tableTextEntry</value>
</data>
<data name="&gt;&gt;textBody.ZOrder" xml:space="preserve">
<value>1</value>
</data>
<data name="tableTextEntry.Location" type="System.Drawing.Point, System.Drawing">
<value>4, 14</value>
</data>
<data name="tableTextEntry.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>3, 2, 3, 2</value>
</data>
<data name="tableTextEntry.RowCount" type="System.Int32, mscorlib">
<value>2</value>
</data>
<data name="tableTextEntry.Size" type="System.Drawing.Size, System.Drawing">
<value>417, 195</value>
</data>
<data name="tableTextEntry.TabIndex" type="System.Int32, mscorlib">
<value>0</value>
</data>
<data name="&gt;&gt;tableTextEntry.Name" xml:space="preserve">
<value>tableTextEntry</value>
</data>
<data name="&gt;&gt;tableTextEntry.Type" xml:space="preserve">
<value>System.Windows.Forms.TableLayoutPanel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;tableTextEntry.Parent" xml:space="preserve">
<value>groupTextEntry</value>
</data>
<data name="&gt;&gt;tableTextEntry.ZOrder" xml:space="preserve">
<value>0</value>
</data>
<data name="tableTextEntry.LayoutSettings" type="System.Windows.Forms.TableLayoutSettings, System.Windows.Forms">
<value>&lt;?xml version="1.0" encoding="utf-16"?&gt;&lt;TableLayoutSettings&gt;&lt;Controls&gt;&lt;Control Name="labelBody" Row="0" RowSpan="1" Column="0" ColumnSpan="1" /&gt;&lt;Control Name="textBody" Row="1" RowSpan="1" Column="0" ColumnSpan="1" /&gt;&lt;/Controls&gt;&lt;Columns Styles="Percent,100" /&gt;&lt;Rows Styles="AutoSize,0,Percent,100,Absolute,25,Absolute,25" /&gt;&lt;/TableLayoutSettings&gt;</value>
</data>
<data name="groupTextEntry.Location" type="System.Drawing.Point, System.Drawing">
<value>3, 104</value>
</data>
<data name="groupTextEntry.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>3, 2, 3, 2</value>
</data>
<data name="groupTextEntry.Padding" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>3, 2, 3, 2</value>
</data>
<data name="groupTextEntry.Size" type="System.Drawing.Size, System.Drawing">
<value>425, 216</value>
</data>
<data name="groupTextEntry.TabIndex" type="System.Int32, mscorlib">
<value>2</value>
</data>
<data name="&gt;&gt;groupTextEntry.Name" xml:space="preserve">
<value>groupTextEntry</value>
</data>
<data name="&gt;&gt;groupTextEntry.Type" xml:space="preserve">
<value>System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;groupTextEntry.Parent" xml:space="preserve">
<value>tableGlobal</value>
</data>
<data name="&gt;&gt;groupTextEntry.ZOrder" xml:space="preserve">
<value>2</value>
</data>
<data name="flowButtons.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
<value>Top, Bottom, Left, Right</value>
</data>
<data name="btnCancel.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="btnCancel.Location" type="System.Drawing.Point, System.Drawing">
<value>341, 2</value>
</data>
<data name="btnCancel.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>3, 2, 3, 2</value>
</data>
<data name="btnCancel.Size" type="System.Drawing.Size, System.Drawing">
<value>81, 33</value>
</data>
<data name="btnCancel.TabIndex" type="System.Int32, mscorlib">
<value>10</value>
</data>
<data name="btnCancel.Text" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="&gt;&gt;btnCancel.Name" xml:space="preserve">
<value>btnCancel</value>
</data>
<data name="&gt;&gt;btnCancel.Type" xml:space="preserve">
<value>System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;btnCancel.Parent" xml:space="preserve">
<value>flowButtons</value>
</data>
<data name="&gt;&gt;btnCancel.ZOrder" xml:space="preserve">
<value>0</value>
</data>
<data name="btnSave.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="btnSave.Location" type="System.Drawing.Point, System.Drawing">
<value>254, 2</value>
</data>
<data name="btnSave.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>3, 2, 3, 2</value>
</data>
<data name="btnSave.Size" type="System.Drawing.Size, System.Drawing">
<value>81, 33</value>
</data>
<data name="btnSave.TabIndex" type="System.Int32, mscorlib">
<value>9</value>
</data>
<data name="btnSave.Text" xml:space="preserve">
<value>Save</value>
</data>
<data name="&gt;&gt;btnSave.Name" xml:space="preserve">
<value>btnSave</value>
</data>
<data name="&gt;&gt;btnSave.Type" xml:space="preserve">
<value>System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;btnSave.Parent" xml:space="preserve">
<value>flowButtons</value>
</data>
<data name="&gt;&gt;btnSave.ZOrder" xml:space="preserve">
<value>1</value>
</data>
<data name="flowButtons.FlowDirection" type="System.Windows.Forms.FlowDirection, System.Windows.Forms">
<value>RightToLeft</value>
</data>
<data name="flowButtons.Location" type="System.Drawing.Point, System.Drawing">
<value>3, 324</value>
</data>
<data name="flowButtons.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>3, 2, 3, 2</value>
</data>
<data name="flowButtons.Size" type="System.Drawing.Size, System.Drawing">
<value>425, 38</value>
</data>
<data name="flowButtons.TabIndex" type="System.Int32, mscorlib">
<value>3</value>
</data>
<data name="&gt;&gt;flowButtons.Name" xml:space="preserve">
<value>flowButtons</value>
</data>
<data name="&gt;&gt;flowButtons.Type" xml:space="preserve">
<value>System.Windows.Forms.FlowLayoutPanel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;flowButtons.Parent" xml:space="preserve">
<value>tableGlobal</value>
</data>
<data name="&gt;&gt;flowButtons.ZOrder" xml:space="preserve">
<value>3</value>
</data>
<data name="tableGlobal.Location" type="System.Drawing.Point, System.Drawing">
<value>5, 5</value>
</data>
<data name="tableGlobal.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>3, 2, 3, 2</value>
</data>
<data name="tableGlobal.RowCount" type="System.Int32, mscorlib">
<value>4</value>
</data>
<data name="tableGlobal.Size" type="System.Drawing.Size, System.Drawing">
<value>431, 364</value>
</data>
<data name="tableGlobal.TabIndex" type="System.Int32, mscorlib">
<value>0</value>
</data>
<data name="&gt;&gt;tableGlobal.Name" xml:space="preserve">
<value>tableGlobal</value>
</data>
<data name="&gt;&gt;tableGlobal.Type" xml:space="preserve">
<value>System.Windows.Forms.TableLayoutPanel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;tableGlobal.Parent" xml:space="preserve">
<value>$this</value>
</data>
<data name="&gt;&gt;tableGlobal.ZOrder" xml:space="preserve">
<value>0</value>
</data>
<data name="tableGlobal.LayoutSettings" type="System.Windows.Forms.TableLayoutSettings, System.Windows.Forms">
<value>&lt;?xml version="1.0" encoding="utf-16"?&gt;&lt;TableLayoutSettings&gt;&lt;Controls&gt;&lt;Control Name="chkEnable" Row="0" RowSpan="1" Column="0" ColumnSpan="1" /&gt;&lt;Control Name="tableDates" Row="1" RowSpan="1" Column="0" ColumnSpan="1" /&gt;&lt;Control Name="groupTextEntry" Row="2" RowSpan="1" Column="0" ColumnSpan="1" /&gt;&lt;Control Name="flowButtons" Row="3" RowSpan="1" Column="0" ColumnSpan="1" /&gt;&lt;/Controls&gt;&lt;Columns Styles="Percent,100" /&gt;&lt;Rows Styles="AutoSize,0,AutoSize,0,Percent,100,AutoSize,0,Absolute,20" /&gt;&lt;/TableLayoutSettings&gt;</value>
</data>
<metadata name="$this.Localizable" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<data name="$this.AutoScaleDimensions" type="System.Drawing.SizeF, System.Drawing">
<value>8, 16</value>
</data>
<data name="$this.ClientSize" type="System.Drawing.Size, System.Drawing">
<value>444, 382</value>
</data>
<data name="$this.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>3, 2, 3, 2</value>
</data>
<data name="$this.MinimumSize" type="System.Drawing.Size, System.Drawing">
<value>458, 415</value>
</data>
<data name="$this.Padding" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>5, 5, 5, 5</value>
</data>
<data name="$this.StartPosition" type="System.Windows.Forms.FormStartPosition, System.Windows.Forms">
<value>CenterParent</value>
</data>
<data name="$this.Text" xml:space="preserve">
<value>Out of Office Assistant for {0}</value>
</data>
<data name="&gt;&gt;$this.Name" xml:space="preserve">
<value>OutOfOfficeDialog</value>
</data>
<data name="&gt;&gt;$this.Type" xml:space="preserve">
<value>System.Windows.Forms.Form, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
</root>

View File

@ -0,0 +1,181 @@
/// 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 Acacia.Stubs;
using Acacia.Utils;
using Acacia.ZPush;
using Microsoft.Office.Interop.Outlook;
using static Acacia.DebugOptions;
namespace Acacia.Features.ReplyFlags
{
[AcaciaOption("Synchronises reply flags between Outlook and Z-Push servers.")]
public class FeatureReplyFlags : Feature
{
public FeatureReplyFlags()
{
}
public override void Startup()
{
if (UpdateEvents)
{
// Watch all mail folders and all items in them
Watcher.WatchFolder(new FolderRegistrationTyped(this, ItemType.MailItem),
(folder) => Watcher.WatchItems<IMailItem>(folder, UpdateReplyStatus, false)
);
}
if (ReadEvent)
{
// As a fallback, add an event handler to update the message when displaying it
MailEvents.Read += UpdateReplyStatus;
}
if (SendEvents)
{
// Hook reply and send events to update local state to server
MailEvents.Reply += OnReply;
MailEvents.ReplyAll += OnReplyAll;
MailEvents.Forward += OnForwarded;
}
}
[AcaciaOption("Enables or disables the handling of update events to mail items. When a mail item is " +
"updated, it is checked to see if the reply flags are up to date. This is the main " +
"mechanism for updating reply flags that change on the server")]
public bool UpdateEvents
{
get { return GetOption(OPTION_UPDATE_EVENTS); }
set { SetOption(OPTION_UPDATE_EVENTS, value); }
}
private static readonly BoolOption OPTION_UPDATE_EVENTS = new BoolOption("FolderEvents", true);
[AcaciaOption("Enables or disables the handling of read events on mail items. If this is enabled, " +
"the reply flag is checked. This is almost guaranteed to work, but has the downside " +
"of only updating the reply flag when an email is opened")]
public bool ReadEvent
{
get { return GetOption(OPTION_READ_EVENT); }
set { SetOption(OPTION_READ_EVENT, value); }
}
private static readonly BoolOption OPTION_READ_EVENT = new BoolOption("ReadEvents", true);
[AcaciaOption("Enables or disables the handling of send events on mail items. If this is enabled, " +
"the reply flag is included in emails sent to the Z-Push server, which will update " +
"the flag on the server")]
public bool SendEvents
{
get { return GetOption(OPTION_SEND_EVENTS); }
set { SetOption(OPTION_SEND_EVENTS, value); }
}
private static readonly BoolOption OPTION_SEND_EVENTS = new BoolOption("SendEvents", true);
[AcaciaOption("Disables the parsing of reply flags on incoming mail items." +
"When this flag is disabled, reply flags coming from the server will be completely ignored.")]
public bool ParseIncoming
{
get { return GetOption(OPTION_INCOMING_PARSE); }
set { SetOption(OPTION_INCOMING_PARSE, value); }
}
private static readonly BoolOption OPTION_INCOMING_PARSE = new BoolOption("IncomingParse", true);
[AcaciaOption("Disables the updating of reply flags on incoming mail items." +
"When this flag is disabled, reply flags coming from the server will be examined, " +
"but not action will be taken on them.")]
public bool UpdateIncoming
{
get { return GetOption(OPTION_INCOMING_UPDATE); }
set { SetOption(OPTION_INCOMING_UPDATE, value); }
}
private static readonly BoolOption OPTION_INCOMING_UPDATE = new BoolOption("IncomingUpdate", true);
[AcaciaOption("Disables the updating of reply flags in outgoing emails." +
"If this option is enabled, reply flags will not be sent to the Z-Push server")]
public bool UpdateOutgoing
{
get { return GetOption(OPTION_OUTGOING_UPDATE); }
set { SetOption(OPTION_OUTGOING_UPDATE, value); }
}
private static readonly BoolOption OPTION_OUTGOING_UPDATE = new BoolOption("OutgoingUpdate", true);
#region Server to outlook
private void UpdateReplyStatus(IMailItem mail)
{
if (!ParseIncoming)
return;
bool update = UpdateIncoming;
// See if the categories contain a reply flag
ReplyFlags flags = ReplyFlags.FromCategory(mail, update);
if (flags != null)
{
if (update)
{
Logger.Instance.Debug(this, "Updating flags: {0}", mail.Subject);
// Update the mail item. This will also save the changed category list
flags.UpdateLocal();
}
}
}
#endregion
#region Outlook to server
private readonly Queue<IMailItem> lastItems = new Queue<IMailItem>();
private void OnReply(IMailItem mail, IMailItem response)
{
SetReplyFlag(mail, response, Verb.REPLIED);
}
private void OnReplyAll(IMailItem mail, IMailItem response)
{
SetReplyFlag(mail, response, Verb.REPLIED_TO_ALL);
}
private void OnForwarded(IMailItem mail, IMailItem response)
{
SetReplyFlag(mail, response, Verb.FORWARDED);
}
private void SetReplyFlag(IMailItem mail, IMailItem response, Verb verb)
{
if (!UpdateOutgoing)
return;
string id = (string)mail.GetProperty(OutlookConstants.PR_ZPUSH_MESSAGE_ID);
using (IFolder folder = mail.Parent)
{
string folderId = (string)folder.GetProperty(OutlookConstants.PR_ZPUSH_FOLDER_ID);
string value = ReplyFlags.VerbToExchange(verb) + "/" + id + "/" + folderId;
Logger.Instance.Trace(this, "Reply header: {0}", value);
response.SetProperty(Constants.ZPUSH_REPLY_HEADER, value);
}
}
#endregion
}
}

View File

@ -0,0 +1,229 @@
/// 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.Text.RegularExpressions;
using System.Threading.Tasks;
using Acacia.Stubs;
using Acacia.Utils;
namespace Acacia.Features.ReplyFlags
{
public enum Verb
{
NONE,
REPLIED,
REPLIED_TO_ALL,
FORWARDED
}
// TODO: unit tests for parsing
public class ReplyFlags
{
private readonly IMailItem _item;
public Verb Verb
{
get;
private set;
}
public DateTime? Date
{
get;
private set;
}
/// <summary>
/// Constructor. Reads the reply state from the mail item's reply properties
/// </summary>
public ReplyFlags(IMailItem item)
{
this._item = item;
ReadFromProperties();
}
/// <summary>
/// Fully initializing constructor.
/// </summary>
private ReplyFlags(IMailItem item, Verb verb, DateTime date)
{
this._item = item;
this.Verb = verb;
this.Date = date;
}
/// <summary>
/// Constructs the ReplyFlags object from the mail's categories, if present.
/// If the category is present, it is removed. Changes are not saved, so this will have to be done explicitly.
/// If no category is present, null is returned.
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
public static ReplyFlags FromCategory(IMailItem item, bool updateCategories = true)
{
string[] categories = item.AttrCategories;
if (categories == null || categories.Length == 0)
return null;
// See if we have the z-push reply header
for (int i = 0; i < categories.Length; ++i)
{
string category = categories[i];
// This test will be invoked on every change, so do a quick test first
if (category.StartsWith(Constants.ZPUSH_REPLY_CATEGORY_PREFIX))
{
string suffix = category.Substring(Constants.ZPUSH_REPLY_CATEGORY_PREFIX.Length);
Match match = Constants.ZPUSH_REPLY_CATEGORY_REGEX.Match(suffix);
if (match.Success)
{
try
{
string dateString = match.Groups[2].Value;
// Parse the state
Verb verb = VerbFromString(match.Groups[1].Value);
// Parse the date
DateTime date = DateTime.Parse(dateString);
// Remove the category
if (updateCategories)
{
var categoriesList = new List<string>(categories);
categoriesList.RemoveAt(i);
item.AttrCategories = categoriesList.ToArray();
}
// Return the flags
return new ReplyFlags(item, verb, date);
}
catch (System.Exception e)
{
// Ignore any exception
Logger.Instance.Error(typeof(ReplyFlags), "Exception while parsing reply category: {0}", e);
}
}
}
}
return null;
}
private void ReadFromProperties()
{
// Read the date
Date = _item.AttrLastVerbExecutionTime;
// And the state
int state = _item.AttrLastVerbExecuted;
switch (state)
{
case OutlookConstants.EXCHIVERB_FORWARD:
Verb = Verb.FORWARDED;
break;
case OutlookConstants.EXCHIVERB_REPLYTOALL:
case OutlookConstants.EXCHIVERB_REPLYTOSENDER:
case OutlookConstants.EXCHIVERB_REPLYTOFOLDER:
Verb = Verb.REPLIED;
break;
default:
Verb = Verb.NONE;
break;
}
}
/// <summary>
/// Updates the local mail item from the current state
/// </summary>
public void UpdateLocal()
{
// Determine icon and verb
int icon = OutlookConstants.PR_ICON_INDEX_NONE;
int verb = OutlookConstants.EXCHIVERB_OPEN;
switch (Verb)
{
case Verb.REPLIED:
icon = OutlookConstants.PR_ICON_INDEX_REPLIED;
verb = OutlookConstants.EXCHIVERB_REPLYTOSENDER;
break;
case Verb.REPLIED_TO_ALL:
icon = OutlookConstants.PR_ICON_INDEX_REPLIED;
verb = OutlookConstants.EXCHIVERB_REPLYTOALL;
break;
case Verb.FORWARDED:
icon = OutlookConstants.PR_ICON_INDEX_FORWARDED;
verb = OutlookConstants.EXCHIVERB_FORWARD;
break;
}
// Set the properties
_item.SetProperties(
new string[]
{
OutlookConstants.PR_ICON_INDEX,
OutlookConstants.PR_LAST_VERB_EXECUTED,
OutlookConstants.PR_LAST_VERB_EXECUTION_TIME
},
new object[]
{
icon,
verb,
Date
}
);
// And save
_item.Save();
}
override public string ToString()
{
return Verb + "=" + Date;
}
public static Verb VerbFromString(string verb)
{
if (verb == Constants.ZPUSH_REPLY_CATEGORY_REPLIED)
return Verb.REPLIED;
else if (verb == Constants.ZPUSH_REPLY_CATEGORY_REPLIED_TO_ALL)
return Verb.REPLIED_TO_ALL;
else if (verb == Constants.ZPUSH_REPLY_CATEGORY_FORWARDED)
return Verb.FORWARDED;
else
throw new System.Exception("Invalid verb: " + verb);
}
public static int VerbToExchange(Verb verb)
{
switch(verb)
{
case Verb.REPLIED:
return OutlookConstants.EXCHIVERB_REPLYTOSENDER;
case Verb.REPLIED_TO_ALL:
return OutlookConstants.EXCHIVERB_REPLYTOALL;
case Verb.FORWARDED:
return OutlookConstants.EXCHIVERB_FORWARD;
default:
throw new System.Exception("Invalid verb: " + verb);
}
}
}
}

View File

@ -0,0 +1,125 @@
/// 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 Acacia.Stubs;
using Acacia.Utils;
using Acacia.ZPush;
using Microsoft.Office.Interop.Outlook;
using Acacia.Features.SharedFolders;
using Acacia.ZPush.API.SharedFolders;
using static Acacia.DebugOptions;
namespace Acacia.Features.SendAs
{
[AcaciaOption("Provides the ability to select different senders for Z-Push accounts.")]
public class FeatureSendAs : FeatureDisabled
{
private FeatureSharedFolders _sharedFolders;
public FeatureSendAs()
{
}
[AcaciaOption("Disables the \"Send As Owner\" feature. This feature allows sending as the owner of a shared folder, " +
"when responding to messages in that folder. Note that this feature requires SharedFolders to be " +
"enabled")]
public bool SendAsOwner
{
get { return GetOption(OPTION_SEND_AS_OWNER); }
set { SetOption(OPTION_SEND_AS_OWNER, value); }
}
private static readonly BoolOption OPTION_SEND_AS_OWNER = new BoolOption("SendAsOwner", true);
public override void Startup()
{
MailEvents.ItemSend += MailEvents_ItemSend;
if (SendAsOwner)
{
// Need shared folders for automatic sender selection
_sharedFolders = ThisAddIn.Instance.GetFeature<FeatureSharedFolders>();
if (_sharedFolders != null)
{
MailEvents.Respond += MailEvents_Respond;
}
}
}
private void MailEvents_Respond(IMailItem mail, IMailItem response)
{
Logger.Instance.Trace(this, "Responding to mail, checking");
using (IStore store = mail.Store)
{
ZPushAccount zpush = Watcher.Accounts.GetAccount(store);
Logger.Instance.Trace(this, "Checking ZPush: {0}", zpush);
if (zpush != null)
{
// Check if the containing folder is a shared folder
using (IFolder parent = mail.Parent)
{
Logger.Instance.Trace(this, "Checking, Parent folder: {0}", parent.Name);
SharedFolder shared = _sharedFolders.GetSharedFolder(parent);
if (shared != null)
Logger.Instance.Trace(this, "Checking, Shared folder: {0}, flags={1}", shared, shared?.Flags);
else
Logger.Instance.Trace(this, "Not a shared folder");
if (shared != null && shared.FlagSendAsOwner)
{
Logger.Instance.Trace(this, "Checking, Shared folder owner: {0}", shared.Store.UserName);
// It's a shared folder, use the owner as the sender if possible
// TODO: make a wrapper for this
var recip = ThisAddIn.Instance.Application.Session.CreateRecipient(shared.Store.UserName);
Logger.Instance.Trace(this, "Checking, Shared folder owner recipient: {0}", recip.Name);
if (recip != null && recip.Resolve())
{
Logger.Instance.Trace(this, "Sending as: {0}", recip.AddressEntry.Address);
response.SetSender(recip.AddressEntry);
}
else
{
Logger.Instance.Trace(this, "Unable to resolve sender");
}
}
}
}
}
}
private void MailEvents_ItemSend(IMailItem item, ref bool cancel)
{
using (IStore store = item.Store)
{
ZPushAccount zpush = Watcher.Accounts.GetAccount(store);
if (zpush != null)
{
string address = item.SenderEmailAddress;
if (address != null && address != zpush.SmtpAddress)
{
Logger.Instance.Trace(this, "SendAs: {0}: {1}", address, item.SenderName);
item.SetProperty(Constants.ZPUSH_SEND_AS, address);
if (item.SenderName != null)
item.SetProperty(Constants.ZPUSH_SEND_AS_NAME, item.SenderName);
}
}
}
}
}
}

View File

@ -0,0 +1,131 @@
/// 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 Acacia.Stubs;
using Acacia.UI;
using Acacia.UI.Outlook;
using Acacia.ZPush;
using Acacia.ZPush.API.SharedFolders;
using Acacia.ZPush.Connect;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Acacia.Features.SharedFolders
{
[AcaciaOption("Provides the ability to open shared folders from other users in Outlook.")]
public class FeatureSharedFolders
:
Feature, FeatureWithRibbon, FeatureWithContextMenu
{
public override void Startup()
{
RegisterButton(this, "SharedFolders", true, ManageFolders, ZPushBehaviour.Disable);
MenuItem<IFolder> menuItem = RegisterMenuItem<IFolder>(this, "SharedFolders_Context", null, ManageFolder, ZPushBehaviour.Hide);
if (menuItem != null)
menuItem.CheckEnabled = CanManageFolder;
// Sync state
Watcher.Sync.AddTask(this, Name, AdditionalFolders_Sync);
}
#region UI
private bool CanManageFolder(MenuItem<IFolder> b, IFolder folder)
{
return folder.SyncId?.IsShared == true;
}
private void ManageFolder(IFolder folder)
{
ZPushAccount account = Watcher.Accounts.GetAccount(folder);
if (account != null)
{
new SharedFoldersDialog(account, folder.SyncId).ShowDialog();
}
}
private void ManageFolders()
{
ZPushAccount account = Watcher.CurrentZPushAccount();
if (account != null)
{
new SharedFoldersDialog(account).ShowDialog();
}
}
#endregion
#region Shared folders sync
private const string KEY_SHARES = "Shares";
private void AdditionalFolders_Sync(ZPushConnection connection)
{
Logger.Instance.Debug(this, "Starting sync for account {0}", connection.Account);
using (SharedFoldersAPI api = new SharedFoldersAPI(connection))
{
// Fetch the current shares
ICollection<SharedFolder> shares = api.GetCurrentShares();
Logger.Instance.Trace(this, "AdditionalFolders_Sync: {0}", shares.Count);
// Convert to dictionary
Dictionary<SyncId, SharedFolder> dict = shares.ToDictionary(x => x.SyncId);
Logger.Instance.Trace(this, "AdditionalFolders_Sync2: {0}", shares.Count);
// Store with the account
connection.Account.SetFeatureData(this, KEY_SHARES, dict);
}
}
public SharedFolder GetSharedFolder(IFolder folder)
{
if (folder == null)
return null;
// Check that we can get the id
SyncId folderId = folder.SyncId;
Logger.Instance.Trace(this, "GetSharedFolder1: {0}", folderId);
if (folderId == null || !folderId.IsShared)
return null;
// Get the ZPush account
ZPushAccount account = Watcher.Accounts.GetAccount(folder);
Logger.Instance.Trace(this, "GetSharedFolder2: {0}", account);
if (account == null)
return null;
// Get the shared folders
Dictionary<SyncId, SharedFolder> shared = account.GetFeatureData<Dictionary<SyncId, SharedFolder>>(this, KEY_SHARES);
Logger.Instance.Trace(this, "GetSharedFolder3: {0}", shared?.Count);
if (shared == null)
return null;
SharedFolder share = null;
shared.TryGetValue(folderId, out share);
Logger.Instance.Trace(this, "GetSharedFolder4: {0}", share);
return share;
}
#endregion
}
}

View File

@ -0,0 +1,95 @@
/// 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 Acacia.Controls;
using Acacia.ZPush;
using Acacia.ZPush.API.SharedFolders;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Acacia.Features.SharedFolders
{
public class FolderTreeNode : KTreeNode
{
private readonly StoreTreeNode _store;
private readonly AvailableFolder _folder;
private SharedFolder _share;
public FolderTreeNode(StoreTreeNode store, AvailableFolder folder, SharedFolder share)
{
this._store = store;
this._folder = folder;
this._share = share;
this.Text = folder.Name;
// Image
// TODO: clean this up
int index = ((int)OutlookConstants.BASIC_SYNC_TYPES[(int)folder.Type]) - 1;
if (index < 0 || index >= store.Owner.Images.Images.Count - 1)
index = 0;
ImageIndex = index;
}
protected override void OnCheckStateChanged()
{
// Update share state
if (CheckState == System.Windows.Forms.CheckState.Unchecked)
_store.RemoveShare(_folder);
else
_share = _store.AddShare(_folder, _share);
base.OnCheckStateChanged();
}
public AvailableFolder AvailableFolder { get { return _folder; } }
public bool IsShared { get { return CheckState != System.Windows.Forms.CheckState.Unchecked; } }
/// <summary>
/// Returns the current share state. Note that this may return a state, even if IsShared is false, as the state is retained,
/// in case the user reselects it. However, if IsShared is true, a valid object is guaranteed.
/// </summary>
public SharedFolder SharedFolder
{
get { return _share; }
set
{
if (value == null)
throw new InvalidOperationException("Cannot unset share");
if (_share != value)
{
_share = value;
_store.AddShare(_folder, _share);
}
}
}
// TODO: this is generally useful, move to KTreeNode
public IEnumerable<FolderTreeNode> Descendants()
{
foreach(KTreeNode child in Children)
{
yield return (FolderTreeNode)child;
foreach (FolderTreeNode desc in ((FolderTreeNode)child).Descendants())
yield return desc;
}
}
}
}

View File

@ -0,0 +1,232 @@
namespace Acacia.Features.SharedFolders
{
partial class SharedFoldersDialog
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(SharedFoldersDialog));
Acacia.Controls.KCheckManager.RecursiveThreeState recursiveThreeState1 = new Acacia.Controls.KCheckManager.RecursiveThreeState();
this._layout = new System.Windows.Forms.TableLayoutPanel();
this._mainBusyHider = new Acacia.Controls.KBusyHider();
this._layoutMain = new System.Windows.Forms.TableLayoutPanel();
this._layoutSelectUser = new System.Windows.Forms.TableLayoutPanel();
this.labelSelectUser = new System.Windows.Forms.Label();
this.buttonOpenUser = new System.Windows.Forms.Button();
this._layoutCenterGABLookup = new System.Windows.Forms.TableLayoutPanel();
this.gabLookup = new Acacia.UI.GABLookupControl();
this.kTreeFolders = new Acacia.Controls.KTree();
this._layoutOptions = new System.Windows.Forms.TableLayoutPanel();
this._labelName = new System.Windows.Forms.Label();
this.textName = new System.Windows.Forms.TextBox();
this._labelSendAs = new System.Windows.Forms.Label();
this.checkSendAs = new System.Windows.Forms.CheckBox();
this._labelPermissions = new System.Windows.Forms.Label();
this.labelPermissionsValue = new System.Windows.Forms.Label();
this.dialogButtons = new Acacia.Controls.KDialogButtons();
this._layout.SuspendLayout();
this._mainBusyHider.SuspendLayout();
this._layoutMain.SuspendLayout();
this._layoutSelectUser.SuspendLayout();
this._layoutCenterGABLookup.SuspendLayout();
this._layoutOptions.SuspendLayout();
this.SuspendLayout();
//
// _layout
//
resources.ApplyResources(this._layout, "_layout");
this._layout.Controls.Add(this._mainBusyHider, 0, 0);
this._layout.Controls.Add(this.dialogButtons, 0, 1);
this._layout.Name = "_layout";
//
// _mainBusyHider
//
this._mainBusyHider.Busy = false;
this._mainBusyHider.BusyText = null;
this._mainBusyHider.Cancellation = null;
this._mainBusyHider.Controls.Add(this._layoutMain);
resources.ApplyResources(this._mainBusyHider, "_mainBusyHider");
this._mainBusyHider.Name = "_mainBusyHider";
//
// _layoutMain
//
resources.ApplyResources(this._layoutMain, "_layoutMain");
this._layoutMain.Controls.Add(this._layoutSelectUser, 0, 0);
this._layoutMain.Controls.Add(this.kTreeFolders, 0, 1);
this._layoutMain.Controls.Add(this._layoutOptions, 0, 2);
this._layoutMain.Name = "_layoutMain";
//
// _layoutSelectUser
//
resources.ApplyResources(this._layoutSelectUser, "_layoutSelectUser");
this._layoutSelectUser.Controls.Add(this.labelSelectUser, 0, 0);
this._layoutSelectUser.Controls.Add(this.buttonOpenUser, 2, 0);
this._layoutSelectUser.Controls.Add(this._layoutCenterGABLookup, 1, 0);
this._layoutSelectUser.Name = "_layoutSelectUser";
//
// labelSelectUser
//
resources.ApplyResources(this.labelSelectUser, "labelSelectUser");
this.labelSelectUser.Name = "labelSelectUser";
//
// buttonOpenUser
//
resources.ApplyResources(this.buttonOpenUser, "buttonOpenUser");
this.buttonOpenUser.Name = "buttonOpenUser";
this.buttonOpenUser.UseVisualStyleBackColor = true;
this.buttonOpenUser.Click += new System.EventHandler(this.buttonOpenUser_Click);
//
// _layoutCenterGABLookup
//
resources.ApplyResources(this._layoutCenterGABLookup, "_layoutCenterGABLookup");
this._layoutCenterGABLookup.Controls.Add(this.gabLookup, 0, 1);
this._layoutCenterGABLookup.Name = "_layoutCenterGABLookup";
//
// gabLookup
//
this.gabLookup.DisplayMember = "DisplayName";
resources.ApplyResources(this.gabLookup, "gabLookup");
this.gabLookup.FormattingEnabled = true;
this.gabLookup.GAB = null;
this.gabLookup.Name = "gabLookup";
this.gabLookup.SelectedUserChanged += new Acacia.UI.GABLookupControl.SelectedUserEventHandler(this.gabLookup_SelectedUserChanged);
//
// kTreeFolders
//
this.kTreeFolders.BackColor = System.Drawing.SystemColors.Window;
this.kTreeFolders.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.kTreeFolders.CheckManager = recursiveThreeState1;
this.kTreeFolders.CheckStyle = Acacia.Controls.KCheckStyle.RecursiveThreeState;
resources.ApplyResources(this.kTreeFolders, "kTreeFolders");
this.kTreeFolders.FullRowSelect = true;
this.kTreeFolders.Images = null;
this.kTreeFolders.MultipleSelection = true;
this.kTreeFolders.Name = "kTreeFolders";
this.kTreeFolders.NodeIndent = 8;
this.kTreeFolders.NodePadding = new System.Windows.Forms.Padding(2, 4, 2, 4);
this.kTreeFolders.CheckStateChanged += new Acacia.Controls.KTree.CheckStateChangedHandler(this.kTreeFolders_CheckStateChanged);
this.kTreeFolders.SelectionChanged += new Acacia.Controls.KTree.SelectionChangedDelegate(this.kTreeFolders_SelectionChanged);
//
// _layoutOptions
//
resources.ApplyResources(this._layoutOptions, "_layoutOptions");
this._layoutOptions.Controls.Add(this._labelName, 0, 0);
this._layoutOptions.Controls.Add(this.textName, 1, 0);
this._layoutOptions.Controls.Add(this._labelSendAs, 0, 1);
this._layoutOptions.Controls.Add(this.checkSendAs, 1, 1);
this._layoutOptions.Controls.Add(this._labelPermissions, 0, 2);
this._layoutOptions.Controls.Add(this.labelPermissionsValue, 1, 2);
this._layoutOptions.Name = "_layoutOptions";
//
// _labelName
//
resources.ApplyResources(this._labelName, "_labelName");
this._labelName.Name = "_labelName";
//
// textName
//
resources.ApplyResources(this.textName, "textName");
this.textName.Name = "textName";
this.textName.TextChanged += new System.EventHandler(this.textName_TextChanged);
//
// _labelSendAs
//
resources.ApplyResources(this._labelSendAs, "_labelSendAs");
this._labelSendAs.Name = "_labelSendAs";
//
// checkSendAs
//
resources.ApplyResources(this.checkSendAs, "checkSendAs");
this.checkSendAs.Name = "checkSendAs";
this.checkSendAs.ThreeState = true;
this.checkSendAs.UseVisualStyleBackColor = true;
this.checkSendAs.CheckedChanged += new System.EventHandler(this.checkSendAs_CheckedChanged);
//
// _labelPermissions
//
resources.ApplyResources(this._labelPermissions, "_labelPermissions");
this._labelPermissions.Name = "_labelPermissions";
//
// labelPermissionsValue
//
resources.ApplyResources(this.labelPermissionsValue, "labelPermissionsValue");
this.labelPermissionsValue.Name = "labelPermissionsValue";
//
// dialogButtons
//
resources.ApplyResources(this.dialogButtons, "dialogButtons");
this.dialogButtons.ButtonSize = null;
this.dialogButtons.Cancellation = null;
this.dialogButtons.HasApply = true;
this.dialogButtons.IsDirty = false;
this.dialogButtons.Name = "dialogButtons";
this.dialogButtons.Apply += new System.EventHandler(this.dialogButtons_Apply);
//
// SharedFoldersDialog
//
resources.ApplyResources(this, "$this");
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BusyHider = this._mainBusyHider;
this.Controls.Add(this._layout);
this.DialogButtons = this.dialogButtons;
this.Name = "SharedFoldersDialog";
this.DirtyFormClosing += new System.Windows.Forms.FormClosingEventHandler(this.SharedFoldersDialog_DirtyFormClosing);
this.Shown += new System.EventHandler(this.AddSharedFolderDialog_Shown);
this._layout.ResumeLayout(false);
this._layout.PerformLayout();
this._mainBusyHider.ResumeLayout(false);
this._layoutMain.ResumeLayout(false);
this._layoutMain.PerformLayout();
this._layoutSelectUser.ResumeLayout(false);
this._layoutSelectUser.PerformLayout();
this._layoutCenterGABLookup.ResumeLayout(false);
this._layoutOptions.ResumeLayout(false);
this._layoutOptions.PerformLayout();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.TableLayoutPanel _layout;
private Controls.KTree kTreeFolders;
private System.Windows.Forms.TableLayoutPanel _layoutSelectUser;
private System.Windows.Forms.Label labelSelectUser;
private System.Windows.Forms.Button buttonOpenUser;
private System.Windows.Forms.TableLayoutPanel _layoutMain;
private System.Windows.Forms.TableLayoutPanel _layoutOptions;
private System.Windows.Forms.Label _labelName;
private System.Windows.Forms.TextBox textName;
private System.Windows.Forms.Label _labelSendAs;
private System.Windows.Forms.CheckBox checkSendAs;
private System.Windows.Forms.Label _labelPermissions;
private System.Windows.Forms.Label labelPermissionsValue;
private Controls.KBusyHider _mainBusyHider;
private Controls.KDialogButtons dialogButtons;
private System.Windows.Forms.TableLayoutPanel _layoutCenterGABLookup;
private UI.GABLookupControl gabLookup;
}
}

View File

@ -0,0 +1,495 @@
/// 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 Acacia.Controls;
using Acacia.Features.GAB;
using Acacia.Stubs;
using Acacia.UI;
using Acacia.UI.Outlook;
using Acacia.Utils;
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;
using System.Xml;
using System.Xml.Serialization;
using static Acacia.ZPush.API.SharedFolders.SharedFoldersAPI;
namespace Acacia.Features.SharedFolders
{
public partial class SharedFoldersDialog : KDialogNew
{
private readonly ZPushAccount _account;
private SyncId _initialSyncId;
private SharedFolder _initialFolder;
public SharedFoldersDialog(ZPushAccount account, SyncId initial = null)
{
this._account = 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;
// Add the email address to the title
Text = string.Format(Text, 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) =>
{
using (SharedFoldersAPI api = new SharedFoldersAPI(_account))
{
// TODO: bind cancellation token to Cancel button
// Fetch current shares
ICollection<SharedFolder> folders = api.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, publicShares, 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, 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);
context.AddBusy(-1);
};
FocusNode(node);
}
}
kTreeFolders.Focus();
}
private void dialogButtons_Apply(object sender, EventArgs e)
{
BusyText = Properties.Resources.SharedFolders_Applying_Label;
KUITask.New((ctx) =>
{
using (SharedFoldersAPI folders = new SharedFoldersAPI(_account))
{
// We reuse the same busy indicationg for all calls. A count is kept to ensure it's removed.
int count = 0;
foreach (StoreTreeNode storeNode in _userFolders.Values)
{
if (storeNode.IsDirty)
{
ctx.AddBusy(1);
++count;
folders.SetCurrentShares(storeNode.User, storeNode.CurrentShares, ctx.CancellationToken);
}
}
return count;
}
})
.OnSuccess((ctx, count) =>
{
foreach (StoreTreeNode storeNode in _userFolders.Values)
if (storeNode.IsDirty)
storeNode.ChangesApplied();
ctx.AddBusy(-count);
// Sync account
_account.SendReceive();
// 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, true);
}
private void gabLookup_SelectedUserChanged(object source, GABLookupControl.SelectedUserEventArgs e)
{
buttonOpenUser.Enabled = (e.SelectedUser != null);
if (e.IsChosen)
{
AddUserFolders(e.SelectedUser, 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, 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);
}
// Add the node
node = new StoreTreeNode(_account, user, user.DisplayName, currentShares ?? new Dictionary<BackendId, SharedFolder>());
node.DirtyChanged += UserSharesChanged;
_userFolders.Add(user, node);
kTreeFolders.RootNodes.Add(node);
}
if (select)
{
FocusNode(node);
}
}
private void FocusNode(KTreeNode node)
{
// Scroll it to the top of the window
kTreeFolders.SelectNode(node, KTree.ScrollMode.Top);
// Start loading folders
node.IsExpanded = true;
// Clear any selected user
gabLookup.SelectedUser = null;
// And focus the tree
kTreeFolders.Focus();
}
private readonly Dictionary<string, bool> _dirtyUsers = new Dictionary<string, bool>();
private void UserSharesChanged(StoreTreeNode node)
{
_dirtyUsers[node.User.UserName] = node.IsDirty;
dialogButtons.IsDirty = _dirtyUsers.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 FolderTreeNode _optionNameNode;
private CheckState? OptionSendAs
{
get
{
if (checkSendAs.Visible)
return checkSendAs.CheckState;
return null;
}
set
{
_labelSendAs.Visible = checkSendAs.Visible = value != null;
if (value != null)
checkSendAs.CheckState = value.Value;
}
}
private readonly List<FolderTreeNode> _optionSendAsNodes = new List<FolderTreeNode>();
private readonly List<bool> _optionSendAsInitial = 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 void ShowOptions(KTreeNode[] nodes)
{
try
{
_layoutOptions.SuspendLayout();
_optionNameNode = null;
_optionSendAsNodes.Clear();
_optionSendAsInitial.Clear();
_optionPermissionNodes.Clear();
OptionName = null;
OptionSendAs = null;
OptionPermissions = null;
foreach (KTreeNode node in nodes)
{
// Ignore the root nodes
if (node is StoreTreeNode)
continue;
FolderTreeNode folderNode = (FolderTreeNode)node;
// Can only set options for shared folders
if (!folderNode.IsShared)
continue;
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;
// Show send as if there are any mail folders
if (folder.IsMailFolder)
{
_optionSendAsNodes.Add(folderNode);
_optionSendAsInitial.Add(folderNode.SharedFolder.FlagSendAsOwner);
}
// Show permissions for all shared nodes
_optionPermissionNodes.Add(folderNode);
}
// Now check consistency of the options
// 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;
}
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;
}
}
}
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);
}
}
private void checkSendAs_CheckedChanged(object sender, EventArgs e)
{
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);
// Send-as is applied recursively
foreach (FolderTreeNode desc in node.Descendants())
{
desc.SharedFolder = desc.SharedFolder.WithFlagSendAsOwner(sendAs);
}
}
}
}
#endregion
}
}

View File

@ -0,0 +1,762 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="mscorlib" name="mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="_layout.ColumnCount" type="System.Int32, mscorlib">
<value>1</value>
</data>
<data name="_layoutMain.ColumnCount" type="System.Int32, mscorlib">
<value>1</value>
</data>
<data name="_layoutSelectUser.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="_layoutSelectUser.AutoSizeMode" type="System.Windows.Forms.AutoSizeMode, System.Windows.Forms">
<value>GrowAndShrink</value>
</data>
<data name="_layoutSelectUser.ColumnCount" type="System.Int32, mscorlib">
<value>3</value>
</data>
<data name="labelSelectUser.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="labelSelectUser.Dock" type="System.Windows.Forms.DockStyle, System.Windows.Forms">
<value>Fill</value>
</data>
<data name="labelSelectUser.ImeMode" type="System.Windows.Forms.ImeMode, System.Windows.Forms">
<value>NoControl</value>
</data>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="labelSelectUser.Location" type="System.Drawing.Point, System.Drawing">
<value>4, 0</value>
</data>
<data name="labelSelectUser.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>4, 0, 4, 0</value>
</data>
<data name="labelSelectUser.Size" type="System.Drawing.Size, System.Drawing">
<value>143, 36</value>
</data>
<data name="labelSelectUser.TabIndex" type="System.Int32, mscorlib">
<value>0</value>
</data>
<data name="labelSelectUser.Text" xml:space="preserve">
<value>Open folders for user</value>
</data>
<data name="labelSelectUser.TextAlign" type="System.Drawing.ContentAlignment, System.Drawing">
<value>MiddleLeft</value>
</data>
<data name="&gt;&gt;labelSelectUser.Name" xml:space="preserve">
<value>labelSelectUser</value>
</data>
<data name="&gt;&gt;labelSelectUser.Type" xml:space="preserve">
<value>System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;labelSelectUser.Parent" xml:space="preserve">
<value>_layoutSelectUser</value>
</data>
<data name="&gt;&gt;labelSelectUser.ZOrder" xml:space="preserve">
<value>0</value>
</data>
<data name="buttonOpenUser.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="buttonOpenUser.AutoSizeMode" type="System.Windows.Forms.AutoSizeMode, System.Windows.Forms">
<value>GrowAndShrink</value>
</data>
<data name="buttonOpenUser.Dock" type="System.Windows.Forms.DockStyle, System.Windows.Forms">
<value>Fill</value>
</data>
<data name="buttonOpenUser.Enabled" type="System.Boolean, mscorlib">
<value>False</value>
</data>
<data name="buttonOpenUser.ImeMode" type="System.Windows.Forms.ImeMode, System.Windows.Forms">
<value>NoControl</value>
</data>
<data name="buttonOpenUser.Location" type="System.Drawing.Point, System.Drawing">
<value>510, 4</value>
</data>
<data name="buttonOpenUser.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>4, 4, 4, 4</value>
</data>
<data name="buttonOpenUser.Padding" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>11, 0, 11, 0</value>
</data>
<data name="buttonOpenUser.Size" type="System.Drawing.Size, System.Drawing">
<value>75, 28</value>
</data>
<data name="buttonOpenUser.TabIndex" type="System.Int32, mscorlib">
<value>1</value>
</data>
<data name="buttonOpenUser.Text" xml:space="preserve">
<value>Open</value>
</data>
<data name="&gt;&gt;buttonOpenUser.Name" xml:space="preserve">
<value>buttonOpenUser</value>
</data>
<data name="&gt;&gt;buttonOpenUser.Type" xml:space="preserve">
<value>System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;buttonOpenUser.Parent" xml:space="preserve">
<value>_layoutSelectUser</value>
</data>
<data name="&gt;&gt;buttonOpenUser.ZOrder" xml:space="preserve">
<value>1</value>
</data>
<data name="_layoutCenterGABLookup.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="_layoutCenterGABLookup.ColumnCount" type="System.Int32, mscorlib">
<value>1</value>
</data>
<data name="gabLookup.Dock" type="System.Windows.Forms.DockStyle, System.Windows.Forms">
<value>Fill</value>
</data>
<data name="gabLookup.FlatStyle" type="System.Windows.Forms.FlatStyle, System.Windows.Forms">
<value>Popup</value>
</data>
<data name="gabLookup.Location" type="System.Drawing.Point, System.Drawing">
<value>4, 4</value>
</data>
<data name="gabLookup.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>4, 4, 4, 4</value>
</data>
<data name="gabLookup.MinimumSize" type="System.Drawing.Size, System.Drawing">
<value>265, 0</value>
</data>
<data name="gabLookup.Size" type="System.Drawing.Size, System.Drawing">
<value>343, 24</value>
</data>
<data name="gabLookup.TabIndex" type="System.Int32, mscorlib">
<value>1</value>
</data>
<data name="&gt;&gt;gabLookup.Name" xml:space="preserve">
<value>gabLookup</value>
</data>
<data name="&gt;&gt;gabLookup.Type" xml:space="preserve">
<value>Acacia.UI.GABLookupControl, Kopano, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null</value>
</data>
<data name="&gt;&gt;gabLookup.Parent" xml:space="preserve">
<value>_layoutCenterGABLookup</value>
</data>
<data name="&gt;&gt;gabLookup.ZOrder" xml:space="preserve">
<value>0</value>
</data>
<data name="_layoutCenterGABLookup.Dock" type="System.Windows.Forms.DockStyle, System.Windows.Forms">
<value>Fill</value>
</data>
<data name="_layoutCenterGABLookup.Location" type="System.Drawing.Point, System.Drawing">
<value>153, 2</value>
</data>
<data name="_layoutCenterGABLookup.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>2, 2, 2, 2</value>
</data>
<data name="_layoutCenterGABLookup.RowCount" type="System.Int32, mscorlib">
<value>3</value>
</data>
<data name="_layoutCenterGABLookup.Size" type="System.Drawing.Size, System.Drawing">
<value>351, 32</value>
</data>
<data name="_layoutCenterGABLookup.TabIndex" type="System.Int32, mscorlib">
<value>2</value>
</data>
<data name="&gt;&gt;_layoutCenterGABLookup.Name" xml:space="preserve">
<value>_layoutCenterGABLookup</value>
</data>
<data name="&gt;&gt;_layoutCenterGABLookup.Type" xml:space="preserve">
<value>System.Windows.Forms.TableLayoutPanel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;_layoutCenterGABLookup.Parent" xml:space="preserve">
<value>_layoutSelectUser</value>
</data>
<data name="&gt;&gt;_layoutCenterGABLookup.ZOrder" xml:space="preserve">
<value>2</value>
</data>
<data name="_layoutCenterGABLookup.LayoutSettings" type="System.Windows.Forms.TableLayoutSettings, System.Windows.Forms">
<value>&lt;?xml version="1.0" encoding="utf-16"?&gt;&lt;TableLayoutSettings&gt;&lt;Controls&gt;&lt;Control Name="gabLookup" Row="1" RowSpan="1" Column="0" ColumnSpan="1" /&gt;&lt;/Controls&gt;&lt;Columns Styles="Percent,100" /&gt;&lt;Rows Styles="Percent,50,AutoSize,0,Percent,50" /&gt;&lt;/TableLayoutSettings&gt;</value>
</data>
<data name="_layoutSelectUser.Dock" type="System.Windows.Forms.DockStyle, System.Windows.Forms">
<value>Fill</value>
</data>
<data name="_layoutSelectUser.Location" type="System.Drawing.Point, System.Drawing">
<value>4, 4</value>
</data>
<data name="_layoutSelectUser.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>4, 4, 4, 4</value>
</data>
<data name="_layoutSelectUser.RowCount" type="System.Int32, mscorlib">
<value>1</value>
</data>
<data name="_layoutSelectUser.Size" type="System.Drawing.Size, System.Drawing">
<value>589, 36</value>
</data>
<data name="_layoutSelectUser.TabIndex" type="System.Int32, mscorlib">
<value>0</value>
</data>
<data name="&gt;&gt;_layoutSelectUser.Name" xml:space="preserve">
<value>_layoutSelectUser</value>
</data>
<data name="&gt;&gt;_layoutSelectUser.Type" xml:space="preserve">
<value>System.Windows.Forms.TableLayoutPanel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;_layoutSelectUser.Parent" xml:space="preserve">
<value>_layoutMain</value>
</data>
<data name="&gt;&gt;_layoutSelectUser.ZOrder" xml:space="preserve">
<value>0</value>
</data>
<data name="_layoutSelectUser.LayoutSettings" type="System.Windows.Forms.TableLayoutSettings, System.Windows.Forms">
<value>&lt;?xml version="1.0" encoding="utf-16"?&gt;&lt;TableLayoutSettings&gt;&lt;Controls&gt;&lt;Control Name="labelSelectUser" Row="0" RowSpan="1" Column="0" ColumnSpan="1" /&gt;&lt;Control Name="buttonOpenUser" Row="0" RowSpan="1" Column="2" ColumnSpan="1" /&gt;&lt;Control Name="_layoutCenterGABLookup" Row="0" RowSpan="1" Column="1" ColumnSpan="1" /&gt;&lt;/Controls&gt;&lt;Columns Styles="AutoSize,0,Percent,100,AutoSize,0" /&gt;&lt;Rows Styles="Percent,100,Absolute,37" /&gt;&lt;/TableLayoutSettings&gt;</value>
</data>
<data name="kTreeFolders.Dock" type="System.Windows.Forms.DockStyle, System.Windows.Forms">
<value>Fill</value>
</data>
<data name="kTreeFolders.Location" type="System.Drawing.Point, System.Drawing">
<value>4, 48</value>
</data>
<data name="kTreeFolders.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>4, 4, 4, 4</value>
</data>
<data name="kTreeFolders.Size" type="System.Drawing.Size, System.Drawing">
<value>589, 377</value>
</data>
<data name="kTreeFolders.TabIndex" type="System.Int32, mscorlib">
<value>1</value>
</data>
<data name="&gt;&gt;kTreeFolders.Name" xml:space="preserve">
<value>kTreeFolders</value>
</data>
<data name="&gt;&gt;kTreeFolders.Type" xml:space="preserve">
<value>Acacia.Controls.KTree, Kopano, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null</value>
</data>
<data name="&gt;&gt;kTreeFolders.Parent" xml:space="preserve">
<value>_layoutMain</value>
</data>
<data name="&gt;&gt;kTreeFolders.ZOrder" xml:space="preserve">
<value>1</value>
</data>
<data name="_layoutOptions.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="_layoutOptions.AutoSizeMode" type="System.Windows.Forms.AutoSizeMode, System.Windows.Forms">
<value>GrowAndShrink</value>
</data>
<data name="_layoutOptions.ColumnCount" type="System.Int32, mscorlib">
<value>2</value>
</data>
<data name="_labelName.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="_labelName.Dock" type="System.Windows.Forms.DockStyle, System.Windows.Forms">
<value>Fill</value>
</data>
<data name="_labelName.Location" type="System.Drawing.Point, System.Drawing">
<value>4, 0</value>
</data>
<data name="_labelName.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>4, 0, 4, 0</value>
</data>
<data name="_labelName.Size" type="System.Drawing.Size, System.Drawing">
<value>102, 30</value>
</data>
<data name="_labelName.TabIndex" type="System.Int32, mscorlib">
<value>0</value>
</data>
<data name="_labelName.Text" xml:space="preserve">
<value>Share as</value>
</data>
<data name="_labelName.TextAlign" type="System.Drawing.ContentAlignment, System.Drawing">
<value>MiddleLeft</value>
</data>
<data name="&gt;&gt;_labelName.Name" xml:space="preserve">
<value>_labelName</value>
</data>
<data name="&gt;&gt;_labelName.Type" xml:space="preserve">
<value>System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;_labelName.Parent" xml:space="preserve">
<value>_layoutOptions</value>
</data>
<data name="&gt;&gt;_labelName.ZOrder" xml:space="preserve">
<value>0</value>
</data>
<data name="textName.Dock" type="System.Windows.Forms.DockStyle, System.Windows.Forms">
<value>Fill</value>
</data>
<data name="textName.Location" type="System.Drawing.Point, System.Drawing">
<value>118, 4</value>
</data>
<data name="textName.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>8, 4, 4, 4</value>
</data>
<data name="textName.Size" type="System.Drawing.Size, System.Drawing">
<value>475, 22</value>
</data>
<data name="textName.TabIndex" type="System.Int32, mscorlib">
<value>1</value>
</data>
<data name="&gt;&gt;textName.Name" xml:space="preserve">
<value>textName</value>
</data>
<data name="&gt;&gt;textName.Type" xml:space="preserve">
<value>System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;textName.Parent" xml:space="preserve">
<value>_layoutOptions</value>
</data>
<data name="&gt;&gt;textName.ZOrder" xml:space="preserve">
<value>1</value>
</data>
<data name="_labelSendAs.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="_labelSendAs.Dock" type="System.Windows.Forms.DockStyle, System.Windows.Forms">
<value>Fill</value>
</data>
<data name="_labelSendAs.Location" type="System.Drawing.Point, System.Drawing">
<value>4, 30</value>
</data>
<data name="_labelSendAs.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>4, 0, 4, 0</value>
</data>
<data name="_labelSendAs.Size" type="System.Drawing.Size, System.Drawing">
<value>102, 34</value>
</data>
<data name="_labelSendAs.TabIndex" type="System.Int32, mscorlib">
<value>2</value>
</data>
<data name="_labelSendAs.Text" xml:space="preserve">
<value>Send as owner</value>
</data>
<data name="_labelSendAs.TextAlign" type="System.Drawing.ContentAlignment, System.Drawing">
<value>MiddleLeft</value>
</data>
<data name="&gt;&gt;_labelSendAs.Name" xml:space="preserve">
<value>_labelSendAs</value>
</data>
<data name="&gt;&gt;_labelSendAs.Type" xml:space="preserve">
<value>System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;_labelSendAs.Parent" xml:space="preserve">
<value>_layoutOptions</value>
</data>
<data name="&gt;&gt;_labelSendAs.ZOrder" xml:space="preserve">
<value>2</value>
</data>
<data name="checkSendAs.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="checkSendAs.Dock" type="System.Windows.Forms.DockStyle, System.Windows.Forms">
<value>Left</value>
</data>
<data name="checkSendAs.Location" type="System.Drawing.Point, System.Drawing">
<value>118, 35</value>
</data>
<data name="checkSendAs.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>8, 5, 4, 4</value>
</data>
<data name="checkSendAs.Padding" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>0, 4, 0, 4</value>
</data>
<data name="checkSendAs.Size" type="System.Drawing.Size, System.Drawing">
<value>18, 25</value>
</data>
<data name="checkSendAs.TabIndex" type="System.Int32, mscorlib">
<value>3</value>
</data>
<data name="&gt;&gt;checkSendAs.Name" xml:space="preserve">
<value>checkSendAs</value>
</data>
<data name="&gt;&gt;checkSendAs.Type" xml:space="preserve">
<value>System.Windows.Forms.CheckBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;checkSendAs.Parent" xml:space="preserve">
<value>_layoutOptions</value>
</data>
<data name="&gt;&gt;checkSendAs.ZOrder" xml:space="preserve">
<value>3</value>
</data>
<data name="_labelPermissions.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="_labelPermissions.Dock" type="System.Windows.Forms.DockStyle, System.Windows.Forms">
<value>Fill</value>
</data>
<data name="_labelPermissions.Location" type="System.Drawing.Point, System.Drawing">
<value>4, 64</value>
</data>
<data name="_labelPermissions.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>4, 0, 4, 0</value>
</data>
<data name="_labelPermissions.Padding" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>0, 5, 0, 4</value>
</data>
<data name="_labelPermissions.Size" type="System.Drawing.Size, System.Drawing">
<value>102, 26</value>
</data>
<data name="_labelPermissions.TabIndex" type="System.Int32, mscorlib">
<value>4</value>
</data>
<data name="_labelPermissions.Text" xml:space="preserve">
<value>Permissions</value>
</data>
<data name="_labelPermissions.TextAlign" type="System.Drawing.ContentAlignment, System.Drawing">
<value>MiddleLeft</value>
</data>
<data name="&gt;&gt;_labelPermissions.Name" xml:space="preserve">
<value>_labelPermissions</value>
</data>
<data name="&gt;&gt;_labelPermissions.Type" xml:space="preserve">
<value>System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;_labelPermissions.Parent" xml:space="preserve">
<value>_layoutOptions</value>
</data>
<data name="&gt;&gt;_labelPermissions.ZOrder" xml:space="preserve">
<value>4</value>
</data>
<data name="labelPermissionsValue.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="labelPermissionsValue.Dock" type="System.Windows.Forms.DockStyle, System.Windows.Forms">
<value>Fill</value>
</data>
<data name="labelPermissionsValue.ImageAlign" type="System.Drawing.ContentAlignment, System.Drawing">
<value>MiddleLeft</value>
</data>
<data name="labelPermissionsValue.Location" type="System.Drawing.Point, System.Drawing">
<value>114, 64</value>
</data>
<data name="labelPermissionsValue.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>4, 0, 4, 0</value>
</data>
<data name="labelPermissionsValue.Padding" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>0, 5, 0, 4</value>
</data>
<data name="labelPermissionsValue.Size" type="System.Drawing.Size, System.Drawing">
<value>479, 26</value>
</data>
<data name="labelPermissionsValue.TabIndex" type="System.Int32, mscorlib">
<value>5</value>
</data>
<data name="labelPermissionsValue.Text" xml:space="preserve">
<value>Permissions</value>
</data>
<data name="labelPermissionsValue.TextAlign" type="System.Drawing.ContentAlignment, System.Drawing">
<value>MiddleLeft</value>
</data>
<data name="&gt;&gt;labelPermissionsValue.Name" xml:space="preserve">
<value>labelPermissionsValue</value>
</data>
<data name="&gt;&gt;labelPermissionsValue.Type" xml:space="preserve">
<value>System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;labelPermissionsValue.Parent" xml:space="preserve">
<value>_layoutOptions</value>
</data>
<data name="&gt;&gt;labelPermissionsValue.ZOrder" xml:space="preserve">
<value>5</value>
</data>
<data name="_layoutOptions.Dock" type="System.Windows.Forms.DockStyle, System.Windows.Forms">
<value>Fill</value>
</data>
<data name="_layoutOptions.Location" type="System.Drawing.Point, System.Drawing">
<value>0, 429</value>
</data>
<data name="_layoutOptions.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>0, 0, 0, 0</value>
</data>
<data name="_layoutOptions.RowCount" type="System.Int32, mscorlib">
<value>3</value>
</data>
<data name="_layoutOptions.Size" type="System.Drawing.Size, System.Drawing">
<value>597, 90</value>
</data>
<data name="_layoutOptions.TabIndex" type="System.Int32, mscorlib">
<value>2</value>
</data>
<data name="&gt;&gt;_layoutOptions.Name" xml:space="preserve">
<value>_layoutOptions</value>
</data>
<data name="&gt;&gt;_layoutOptions.Type" xml:space="preserve">
<value>System.Windows.Forms.TableLayoutPanel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;_layoutOptions.Parent" xml:space="preserve">
<value>_layoutMain</value>
</data>
<data name="&gt;&gt;_layoutOptions.ZOrder" xml:space="preserve">
<value>2</value>
</data>
<data name="_layoutOptions.LayoutSettings" type="System.Windows.Forms.TableLayoutSettings, System.Windows.Forms">
<value>&lt;?xml version="1.0" encoding="utf-16"?&gt;&lt;TableLayoutSettings&gt;&lt;Controls&gt;&lt;Control Name="_labelName" Row="0" RowSpan="1" Column="0" ColumnSpan="1" /&gt;&lt;Control Name="textName" Row="0" RowSpan="1" Column="1" ColumnSpan="1" /&gt;&lt;Control Name="_labelSendAs" Row="1" RowSpan="1" Column="0" ColumnSpan="1" /&gt;&lt;Control Name="checkSendAs" Row="1" RowSpan="1" Column="1" ColumnSpan="1" /&gt;&lt;Control Name="_labelPermissions" Row="2" RowSpan="1" Column="0" ColumnSpan="1" /&gt;&lt;Control Name="labelPermissionsValue" Row="2" RowSpan="1" Column="1" ColumnSpan="1" /&gt;&lt;/Controls&gt;&lt;Columns Styles="AutoSize,0,Percent,100" /&gt;&lt;Rows Styles="AutoSize,0,AutoSize,0,AutoSize,0" /&gt;&lt;/TableLayoutSettings&gt;</value>
</data>
<data name="_layoutMain.Dock" type="System.Windows.Forms.DockStyle, System.Windows.Forms">
<value>Fill</value>
</data>
<data name="_layoutMain.Location" type="System.Drawing.Point, System.Drawing">
<value>0, 0</value>
</data>
<data name="_layoutMain.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>4, 4, 4, 4</value>
</data>
<data name="_layoutMain.RowCount" type="System.Int32, mscorlib">
<value>3</value>
</data>
<data name="_layoutMain.Size" type="System.Drawing.Size, System.Drawing">
<value>597, 519</value>
</data>
<data name="_layoutMain.TabIndex" type="System.Int32, mscorlib">
<value>3</value>
</data>
<data name="&gt;&gt;_layoutMain.Name" xml:space="preserve">
<value>_layoutMain</value>
</data>
<data name="&gt;&gt;_layoutMain.Type" xml:space="preserve">
<value>System.Windows.Forms.TableLayoutPanel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;_layoutMain.Parent" xml:space="preserve">
<value>_mainBusyHider</value>
</data>
<data name="&gt;&gt;_layoutMain.ZOrder" xml:space="preserve">
<value>0</value>
</data>
<data name="_layoutMain.LayoutSettings" type="System.Windows.Forms.TableLayoutSettings, System.Windows.Forms">
<value>&lt;?xml version="1.0" encoding="utf-16"?&gt;&lt;TableLayoutSettings&gt;&lt;Controls&gt;&lt;Control Name="_layoutSelectUser" Row="0" RowSpan="1" Column="0" ColumnSpan="1" /&gt;&lt;Control Name="kTreeFolders" Row="1" RowSpan="1" Column="0" ColumnSpan="1" /&gt;&lt;Control Name="_layoutOptions" Row="2" RowSpan="1" Column="0" ColumnSpan="1" /&gt;&lt;/Controls&gt;&lt;Columns Styles="Percent,100" /&gt;&lt;Rows Styles="AutoSize,0,Percent,100,AutoSize,0" /&gt;&lt;/TableLayoutSettings&gt;</value>
</data>
<data name="_mainBusyHider.Dock" type="System.Windows.Forms.DockStyle, System.Windows.Forms">
<value>Fill</value>
</data>
<data name="_mainBusyHider.Location" type="System.Drawing.Point, System.Drawing">
<value>4, 4</value>
</data>
<data name="_mainBusyHider.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>4, 4, 4, 4</value>
</data>
<data name="_mainBusyHider.Size" type="System.Drawing.Size, System.Drawing">
<value>597, 519</value>
</data>
<data name="_mainBusyHider.TabIndex" type="System.Int32, mscorlib">
<value>4</value>
</data>
<data name="_mainBusyHider.Text" type="System.Resources.ResXNullRef, System.Windows.Forms">
<value />
</data>
<data name="&gt;&gt;_mainBusyHider.Name" xml:space="preserve">
<value>_mainBusyHider</value>
</data>
<data name="&gt;&gt;_mainBusyHider.Type" xml:space="preserve">
<value>Acacia.Controls.KBusyHider, Kopano, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null</value>
</data>
<data name="&gt;&gt;_mainBusyHider.Parent" xml:space="preserve">
<value>_layout</value>
</data>
<data name="&gt;&gt;_mainBusyHider.ZOrder" xml:space="preserve">
<value>0</value>
</data>
<data name="dialogButtons.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="dialogButtons.AutoSizeMode" type="System.Windows.Forms.AutoSizeMode, System.Windows.Forms">
<value>GrowAndShrink</value>
</data>
<data name="dialogButtons.Dock" type="System.Windows.Forms.DockStyle, System.Windows.Forms">
<value>Fill</value>
</data>
<data name="dialogButtons.Location" type="System.Drawing.Point, System.Drawing">
<value>2, 528</value>
</data>
<data name="dialogButtons.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>2, 1, 2, 1</value>
</data>
<data name="dialogButtons.Size" type="System.Drawing.Size, System.Drawing">
<value>601, 39</value>
</data>
<data name="dialogButtons.TabIndex" type="System.Int32, mscorlib">
<value>5</value>
</data>
<data name="&gt;&gt;dialogButtons.Name" xml:space="preserve">
<value>dialogButtons</value>
</data>
<data name="&gt;&gt;dialogButtons.Type" xml:space="preserve">
<value>Acacia.Controls.KDialogButtons, Kopano, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null</value>
</data>
<data name="&gt;&gt;dialogButtons.Parent" xml:space="preserve">
<value>_layout</value>
</data>
<data name="&gt;&gt;dialogButtons.ZOrder" xml:space="preserve">
<value>1</value>
</data>
<data name="_layout.Dock" type="System.Windows.Forms.DockStyle, System.Windows.Forms">
<value>Fill</value>
</data>
<data name="_layout.Location" type="System.Drawing.Point, System.Drawing">
<value>8, 7</value>
</data>
<data name="_layout.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>0, 0, 0, 0</value>
</data>
<data name="_layout.RowCount" type="System.Int32, mscorlib">
<value>2</value>
</data>
<data name="_layout.Size" type="System.Drawing.Size, System.Drawing">
<value>605, 568</value>
</data>
<data name="_layout.TabIndex" type="System.Int32, mscorlib">
<value>0</value>
</data>
<data name="&gt;&gt;_layout.Name" xml:space="preserve">
<value>_layout</value>
</data>
<data name="&gt;&gt;_layout.Type" xml:space="preserve">
<value>System.Windows.Forms.TableLayoutPanel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;_layout.Parent" xml:space="preserve">
<value>$this</value>
</data>
<data name="&gt;&gt;_layout.ZOrder" xml:space="preserve">
<value>0</value>
</data>
<data name="_layout.LayoutSettings" type="System.Windows.Forms.TableLayoutSettings, System.Windows.Forms">
<value>&lt;?xml version="1.0" encoding="utf-16"?&gt;&lt;TableLayoutSettings&gt;&lt;Controls&gt;&lt;Control Name="_mainBusyHider" Row="0" RowSpan="1" Column="0" ColumnSpan="1" /&gt;&lt;Control Name="dialogButtons" Row="1" RowSpan="1" Column="0" ColumnSpan="1" /&gt;&lt;/Controls&gt;&lt;Columns Styles="Percent,100" /&gt;&lt;Rows Styles="Percent,100,AutoSize,0,Absolute,25" /&gt;&lt;/TableLayoutSettings&gt;</value>
</data>
<metadata name="$this.Localizable" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<data name="$this.AutoScaleDimensions" type="System.Drawing.SizeF, System.Drawing">
<value>8, 16</value>
</data>
<data name="$this.ClientSize" type="System.Drawing.Size, System.Drawing">
<value>621, 582</value>
</data>
<data name="$this.Padding" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>8, 7, 8, 7</value>
</data>
<data name="$this.StartPosition" type="System.Windows.Forms.FormStartPosition, System.Windows.Forms">
<value>CenterParent</value>
</data>
<data name="$this.Text" xml:space="preserve">
<value>Shared Folders - {0}</value>
</data>
<data name="&gt;&gt;$this.Name" xml:space="preserve">
<value>SharedFoldersDialog</value>
</data>
<data name="&gt;&gt;$this.Type" xml:space="preserve">
<value>Acacia.Controls.KDialogNew, Kopano, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null</value>
</data>
</root>

View File

@ -0,0 +1,320 @@
/// 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 Acacia.Controls;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Acacia.ZPush;
using Acacia.Utils;
using System.Threading;
using Acacia.ZPush.API.SharedFolders;
using Acacia.ZPush.Connect;
using Acacia.Native;
namespace Acacia.Features.SharedFolders
{
/// <summary>
/// A tree node representing the root node for a store. Responsible for loading the store contents and managing the
/// shares for that store.
/// </summary>
public class StoreTreeNode : KTreeNode
{
private KAnimator _reloader;
// The initial and current shares states. The initial state is kept to check for modifications
private readonly Dictionary<BackendId, SharedFolder> _initialShares;
private readonly Dictionary<BackendId, SharedFolder> _currentShares;
public StoreTreeNode(ZPushAccount account, GABUser user, string text, Dictionary<BackendId, SharedFolder> currentFolders)
:
base(text)
{
this._initialShares = currentFolders;
// Create an empty current state. When loading the nodes, the shares will be added. This has the benefit of
// cleaning up automatically any obsolote shares.
this._currentShares = new Dictionary<BackendId, SharedFolder>();
ChildLoader = new UserFolderLoader(this, account, user);
ChildLoader.ReloadOnCloseOpen = true;
HasCheckBox = false;
// TODO: better icons, better way of handling this
ImageIndex = user == GABUser.USER_PUBLIC ? 0 : 11;
// Reloader
_reloader = new KAnimator();
_reloader.Animation = Properties.Resources.TreeLoading;
_reloader.Visible = false;
_reloader.Click += (s, e) =>
{
ChildLoader.Reload();
};
Control = _reloader;
}
public GABUser User
{
get { return ((UserFolderLoader)ChildLoader).User; }
}
#region Share management
/// <summary>
/// Adds a share.
/// </summary>
/// <param name="folder">The folder to share.</param>
/// <param name="state">The share state. This may be null to add a default share</param>
/// <returns>The share information</returns>
internal SharedFolder AddShare(AvailableFolder folder, SharedFolder state)
{
state = state ?? CreateDefaultShare(folder);
_currentShares[folder.BackendId] = state;
CheckDirty();
return state;
}
private SharedFolder CreateDefaultShare(AvailableFolder folder)
{
SharedFolder share = new SharedFolder(folder);
// Default send as for mail folders
if (folder.IsMailFolder)
share = share.WithFlagSendAsOwner(true);
// Default include the store name in root folders
if (folder.ParentId.IsNone)
share = share.WithName(folder.Store.UserName + " - " + folder.Name);
return share;
}
internal void RemoveShare(AvailableFolder folder)
{
if (_currentShares.Remove(folder.BackendId))
{
CheckDirty();
}
}
private SharedFolder GetInitialShareState(AvailableFolder folder)
{
SharedFolder state;
if (_initialShares.TryGetValue(folder.BackendId, out state))
{
return state;
}
return null;
}
public ICollection<SharedFolder> CurrentShares
{
get { return _currentShares.Values; }
}
#endregion
#region Dirty tracking
public delegate void DirtyChangedHandler(StoreTreeNode node);
public event DirtyChangedHandler DirtyChanged;
public bool IsDirty { get; private set; }
private void CheckDirty()
{
bool newDirty = !_initialShares.SameElements(_currentShares);
if (newDirty != IsDirty)
{
IsDirty = newDirty;
if (DirtyChanged != null)
DirtyChanged(this);
}
}
public void ChangesApplied()
{
// Save a copy of current folders to initial folders
_initialShares.Clear();
foreach (var entry in _currentShares)
{
_initialShares.Add(entry.Key, entry.Value);
}
CheckDirty();
}
#endregion
#region Node loading
public class UserFolderLoader : KTreeNodeLoader
{
private readonly ZPushAccount _account;
public GABUser User { get; private set; }
public UserFolderLoader(StoreTreeNode parent, ZPushAccount account, GABUser user) : base(parent)
{
this._account = account;
this.User = user;
}
protected override object DoLoadChildren(KTreeNode node)
{
using (SharedFoldersAPI folders = new SharedFoldersAPI(_account))
{
return folders.GetUserFolders(User);
}
}
private class FolderComparer : IComparer<AvailableFolder>
{
private bool _isRoot;
public FolderComparer(bool isRoot)
{
this._isRoot = isRoot;
}
public int Compare(AvailableFolder x, AvailableFolder y)
{
if (_isRoot)
{
int i = (int)x.Type - (int)y.Type;
if (i != 0)
return i;
}
return x.Name.CompareTo(y.Name);
}
}
protected override void DoRenderChildren(KTreeNode node, object loaded, KTreeNodes children)
{
List<AvailableFolder> folders = (List<AvailableFolder>)loaded;
foreach (AvailableFolder folder in folders.OrderBy(f => f, new FolderComparer(true)))
{
AddFolderNode(node, children, folder);
}
}
private void AddFolderNode(KTreeNode node, KTreeNodes children, AvailableFolder folder)
{
StoreTreeNode rootNode = (StoreTreeNode)this.Children.Parent;
// Create the tree node
SharedFolder share = rootNode.GetInitialShareState(folder);
FolderTreeNode child = new FolderTreeNode(rootNode, folder, share);
// Add
children.Add(child);
// Add the children
foreach (AvailableFolder childFolder in folder.Children.OrderBy(f => f, new FolderComparer(false)))
{
AddFolderNode(child, child.Children, childFolder);
}
// Set the initial share state
if (share != null)
{
child.IsChecked = true;
}
// Add the share; it might have become checked by any of the child nodes
if (child.IsShared)
rootNode.AddShare(folder, share);
}
protected override void OnBeginLoading(KTreeNode node)
{
base.OnBeginLoading(node);
((StoreTreeNode)node)._reloader.Visible = true;
((StoreTreeNode)node)._reloader.Animate = true;
}
protected override void OnEndLoading(KTreeNode node)
{
((StoreTreeNode)node)._reloader.Animate = false;
((StoreTreeNode)node)._reloader.Visible = false;
base.OnEndLoading(node);
((StoreTreeNode)node).OnNodesLoaded();
}
protected override string GetPlaceholderText(LoadingState state, KTreeNodes children)
{
switch (state)
{
case KTreeNodeLoader.LoadingState.Error:
return Properties.Resources.SharedFolders_Loading_Error;
case KTreeNodeLoader.LoadingState.Loading:
return Properties.Resources.SharedFolders_Loading;
case KTreeNodeLoader.LoadingState.Loaded:
if (children.Count == 0)
return Properties.Resources.SharedFolders_None;
return null;
}
return null;
}
}
/// <summary>
/// Event handler for the first time nodes are loaded; not invoked on reload.
/// </summary>
public delegate void NodesLoadedHandler(StoreTreeNode node);
public event NodesLoadedHandler NodesLoaded;
virtual protected void OnNodesLoaded()
{
if (NodesLoaded != null)
{
NodesLoaded(this);
NodesLoaded = null;
}
}
#endregion
#region Node finding
public KTreeNode FindNode(SharedFolder folder)
{
return FindNode(this, folder);
}
private KTreeNode FindNode(KTreeNode node, SharedFolder folder)
{
// TODO: use an index for this? For now it's used only to select the initial node. It might also be useful in KTree
// in a more general way
foreach(FolderTreeNode child in node.Children)
{
if (child.AvailableFolder.BackendId == folder.BackendId)
return child;
KTreeNode found = FindNode(child, folder);
if (found != null)
return found;
}
return null;
}
#endregion
}
}

View File

@ -0,0 +1,143 @@
/// 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 Acacia.UI;
using Acacia.UI.Outlook;
using Acacia.Utils;
using Acacia.ZPush;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Acacia.Features.WebApp
{
[AcaciaOption("Provides the ability to open Kopano WebApp from within Outlook.")]
public class FeatureWebApp : Feature, FeatureWithRibbon
{
private RibbonButton _button;
public override void Startup()
{
_button = RegisterButton(this, "WebApp", true, OpenWebApp, ZPushBehaviour.None);
// Start autodiscover
Watcher.AccountDiscovered += Watcher_AccountDiscovered;
Watcher.ZPushAccountChange += AccountChange;
}
private void Watcher_AccountDiscovered(ZPushAccount account)
{
// Start an autodiscover for each account
AutoDiscover(account);
}
private void AccountChange(ZPushAccount account)
{
if (_button != null)
{
bool enabled = account != null;
if (enabled)
{
// Hide the button if the url could not be fetched
enabled = AutoDiscover(account) != null;
}
_button.IsEnabled = enabled;
}
}
private const string TXT_KDISCOVER = "kdiscover";
private class URLCached
{
public readonly string Url;
public readonly DateTime Date;
public URLCached(string url)
{
this.Url = url;
this.Date = DateTime.Now;
}
}
private void Check_AutoDiscover(ZPushAccount account)
{
AutoDiscover(account);
// Update button state
AccountChange(account);
}
private void OpenWebApp()
{
ZPushAccount account = Watcher.CurrentZPushAccount();
if (account == null)
return;
// Get the url
string url = AutoDiscover(account);
if (url == null)
return;
// Open the browser
System.Diagnostics.Process.Start(url);
}
private string AutoDiscover(ZPushAccount account)
{
// Check for a cached entry
URLCached cached = account.GetFeatureData<URLCached>(this, TXT_KDISCOVER);
// Only cache actual URLs, not missing urls
if (cached != null)
return cached.Url;
// Perform a cached auto discover
try
{
Logger.Instance.Debug(this, "Starting kdiscover: {0}", account.DomainName);
string url = PerformAutoDiscover(account);
Logger.Instance.Debug(this, "Finished kdiscover: {0}: {1}", account.DomainName, url);
account.SetFeatureData(this, TXT_KDISCOVER, new URLCached(url));
return url;
}
catch (Exception e)
{
Logger.Instance.Warning(this, "Exception during kdiscover: {0}: {1}", account.DomainName, e);
account.SetFeatureData(this, TXT_KDISCOVER, null);
return null;
}
}
private string PerformAutoDiscover(ZPushAccount account)
{
// Fetch the txt record
List<string> txt = DnsUtil.GetTxtRecord(account.DomainName);
if (txt == null)
return null;
// Find kdiscover
string kdiscover = txt.FirstOrDefault((record) => record.StartsWith(TXT_KDISCOVER));
if (string.IsNullOrEmpty(kdiscover))
return null;
string url = kdiscover.Substring(TXT_KDISCOVER.Length + 1).Trim();
if (string.IsNullOrWhiteSpace(url))
return null;
return url;
}
}
}

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="NLog" version="4.2.3" targetFramework="net452" />
</packages>

View File

@ -0,0 +1,178 @@
/// 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 Acacia.Utils;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static Acacia.DebugOptions;
namespace Acacia
{
public class GlobalOptions
{
/// <summary>
/// A singleton is used so the property debugger can access the fields.
/// </summary>
public static readonly GlobalOptions INSTANCE = new GlobalOptions();
[AcaciaOption("Completely enables or disables the Outlook plugin. Note that if the plugin is enabled, individual " +
"features may still have to be enabled.")]
virtual public bool Enabled
{
get { return GetOption(null, ENABLED); }
set { SetOption(null, ENABLED, value); }
}
[AcaciaOption("Sets the threading model for long running tasks. MainThread means all tasks are executed " +
"in Outlook's main thread. This is the standard option, but has the effect of locking the UI " +
"for short periods of time. The Background threading model prevents this, but is currently " +
"experimental. The Synchronous option means all tasks are executed synchronously, which " +
"locks up the UI quite frequently, but is the safest option.")]
public Threading Threading
{
get { return GetOption(null, THREADING); }
set { SetOption(null, THREADING, value); }
}
private static readonly EnumOption<Threading> THREADING = new EnumOption<Threading>("Threading");
[AcaciaOption("Enables or disables ZPush account checking. To enable advanced features, it must be known " +
"which accounts use ZPush servers. This option checks responses from ActiveSync servers to " +
"identify the ZPush ones.")]
public bool ZPushCheck
{
get { return GetOption(null, ZPUSH_CHECK); }
set { SetOption(null, ZPUSH_CHECK, value); }
}
private static readonly BoolOption ZPUSH_CHECK = new BoolOption("ZPushCheck", true);
[AcaciaOption("Enables or disables the account timer. Outlook doesn't notify the plugin of new or removed " +
"accounts. The timer is used to periodically check for modified accounts. This is needed to " +
"accurately detect ZPush accounts.")]
public bool AccountTimer
{
get { return GetOption(null, ACCOUNT_TIMER); }
set { SetOption(null, ACCOUNT_TIMER, value); }
}
private static readonly BoolOption ACCOUNT_TIMER = new BoolOption("AccountTimer", true);
[AcaciaOption("Enables or disables ZPush synchronization tasks. These are used to ensure Oulook " +
"has the latest data from the server.")]
public bool ZPushSync
{
get { return GetOption(null, ZPUSH_SYNC); }
set { SetOption(null, ZPUSH_SYNC, value); }
}
private static readonly BoolOption ZPUSH_SYNC = new BoolOption("ZPushSync", true);
[AcaciaOption("Sets the interval at which ZPush synchronization tasks will be executed.")]
public TimeSpan ZPushSync_Period
{
get { return GetOption(null, ZPUSH_SYNC_PERIOD); }
set { SetOption(null, ZPUSH_SYNC_PERIOD, value); }
}
private static readonly TimeSpanOption ZPUSH_SYNC_PERIOD = new TimeSpanOption("ZPushSyncPeriod", Constants.ZPUSH_SYNC_DEFAULT_PERIOD);
[AcaciaOption("Disables the release of COM objects. This generally leads to resource leaks and should " +
"only be disabled for debug purposes.")]
public bool COMRelease
{
get { return GetOption(null, COM_RELEASE); }
set { SetOption(null, COM_RELEASE, value); }
}
private static readonly BoolOption COM_RELEASE = new BoolOption("COMRelease", true);
[AcaciaOption("Enables or disables logging completely.")]
public bool Logging
{
get { return GetOption(null, LOGGING); }
set { SetOption(null, LOGGING, value); }
}
private static readonly BoolOption LOGGING = new BoolOption("Logging", true);
[AcaciaOption("Sets the level of messages that will be logged. For production use, Info should be enough. " +
"The log level may be set higher if there are issues that need to be debugged.")]
public LogLevel Logging_Level
{
get { return Logger.Instance.MinLevel; }
set
{
Logger.Instance.SetLevel(value);
}
}
#region UI Options
[AcaciaOption("Completely enables or disables modifications to the Outlook UI." +
"Note that where applicable, the Ribbon and Context Menu options also control UI modifications, " +
"as do individual features.")]
virtual public bool UI
{
get { return GetOption(null, OUTLOOK_UI); }
set { SetOption(null, OUTLOOK_UI, value); }
}
[AcaciaOption("Completely enables or disables modifications to the Outlook Ribbon." +
"Note that if the UI option is disabled, Ribbon modifications will not be made either.")]
virtual public bool UI_Ribbon
{
get { return GetOption(null, OUTLOOK_UI_RIBBON); }
set { SetOption(null, OUTLOOK_UI_RIBBON, value); }
}
[AcaciaOption("Completely enables or disables modifications to the Outlook Context Menus." +
"Note that if the UI option is disabled, Context Menu modifications will not be made either.")]
virtual public bool UI_ContextMenu
{
get { return GetOption(null, OUTLOOK_UI_CONTEXT_MENU); }
set { SetOption(null, OUTLOOK_UI_CONTEXT_MENU, value); }
}
#endregion
#region Local folders
[AcaciaOption("If this option is enabled, any local folders created in the local store are hidden. " +
"This prevents them from showing up in the Outlook folder list. " +
"Note that this applies only to folders created automatically by Outlook, relevant " +
"folders will still be visible.")]
public bool LocalFolders_Hide
{
get { return GetOption(null, OPTION_HIDE_LOCAL_FOLDERS); }
set { SetOption(null, OPTION_HIDE_LOCAL_FOLDERS, value); }
}
private static readonly BoolOption OPTION_HIDE_LOCAL_FOLDERS = new BoolOption("HideLocalFolders", true);
[AcaciaOption("Specifies the location in which to store local folders. Note that changing this option " +
"does not migrate any existing stores. Setting an invalid path will most likely lead to " +
"errors when starting Outlook. Environment variables such as %APPDATA% can be used to " +
"specify the path.")]
public string LocalFolders_Path
{
get { return RegistryUtil.GetConfigValue(null, "LocalStorePath", (string)null); }
set
{
RegistryUtil.SetConfigValue(null, "LocalStorePath", value, Microsoft.Win32.RegistryValueKind.String);
}
}
#endregion
}
}

View File

@ -0,0 +1,157 @@
/// 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 Acacia.Features;
using Acacia.Utils;
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Acacia
{
public interface LogContext
{
string LogContextId { get; }
}
public abstract class Logger
{
public static readonly Logger Instance = GlobalOptions.INSTANCE.Logging
? (Logger)new NLogLogger(LibUtils.AssemblyName)
: (Logger)new NullLogger();
virtual public string Path
{
get { return null; }
}
private LogLevel _minLevel = LogLevel.Trace;
public LogLevel MinLevel
{
get { return _minLevel; }
}
public void Initialize()
{
try
{
_minLevel = (LogLevel)RegistryUtil.GetConfigValue<int>(null, Constants.PLUGIN_REGISTRY_LOGLEVEL, (int)_minLevel);
OnLogLevelChanged();
}
catch (Exception) { }
DoLog(_minLevel, this, "Level initialized", null);
}
public void SetLevel(LogLevel level)
{
if (level != _minLevel)
{
_minLevel = level;
RegistryUtil.SetConfigValue(null, Constants.PLUGIN_REGISTRY_LOGLEVEL, (int)level, RegistryValueKind.DWord);
OnLogLevelChanged();
}
}
virtual protected void OnLogLevelChanged() { }
#region Loggers
public void TraceExtra(object context, string format, params object[] args)
{
DoLog(LogLevel.TraceExtra, context, format, args);
}
public void Trace(object context, string format, params object[] args)
{
DoLog(LogLevel.Trace, context, format, args);
}
public void Debug(object context, string format, params object[] args)
{
DoLog(LogLevel.Debug, context, format, args);
}
public void Info(object context, string format, params object[] args)
{
DoLog(LogLevel.Info, context, format, args);
}
public void Warning(object context, string format, params object[] args)
{
DoLog(LogLevel.Warning, context, format, args);
}
public void Error(object context, string format, params object[] args)
{
DoLog(LogLevel.Error, context, format, args);
}
public void Fatal(object context, string format, params object[] args)
{
DoLog(LogLevel.Fatal, context, format, args);
}
#endregion
#region Implementation
protected abstract void DoLogMessage(LogLevel level, string message);
private void DoLog(LogLevel level, object context, string format, object[] args)
{
if (!IsLevelEnabled(level))
return;
// Message
string msg = args == null || args.Length == 0 ? format : String.Format(format, args);
// Include context
if (context is LogContext)
msg = ((LogContext)context).LogContextId + ": " + msg;
else if (context is Type)
msg = ((Type)context).Name + ": " + msg;
else if (context is string)
msg = ((string)context) + ": " + msg;
else if (context != null)
msg = context.GetType().Name + ": " + msg;
// Include level
msg = level.ToString() + ": " + msg;
// Report
DoLogMessage(level, msg);
}
public bool IsLevelEnabled(LogLevel level)
{
return level <= MinLevel;
}
#endregion
}
public class NullLogger
:
Logger
{
protected override void DoLogMessage(LogLevel level, string message)
{
}
}
}

View File

@ -0,0 +1,41 @@
/// 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.Text;
namespace Acacia
{
public enum LogLevel
{
Fatal,
Error,
Warning,
Info,
Debug,
Trace,
TraceExtra
}
public static class LoggerHelpers
{
public static string LoggerPath(string name)
{
return System.IO.Path.Combine(System.IO.Path.GetTempPath(), name + ".log");
}
}
}

View File

@ -0,0 +1,101 @@
/// 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 NLog.Config;
using NLog.Targets;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NLog;
namespace Acacia
{
class NLogLogger : Logger
{
private readonly NLog.Logger _impl;
private readonly string _path;
internal NLogLogger(string name)
{
_path = LoggerHelpers.LoggerPath(name);
FileTarget file = new FileTarget();
file.MaxArchiveFiles = 10;
file.ArchiveAboveSize = 1 * 1024 * 1024;
file.CreateDirs = true;
file.FileName = _path;
file.Layout = "${date} (${threadid},${threadname}): ${message}";
LoggingConfiguration config = new LoggingConfiguration();
config.AddTarget("file", file);
config.LoggingRules.Add(new LoggingRule("*", file));
DebuggerTarget debug = new DebuggerTarget();
debug.Layout = file.Layout;
config.AddTarget("debug", debug);
config.LoggingRules.Add(new LoggingRule("*", debug));
NLog.LogManager.Configuration = config;
OnLogLevelChanged();
_impl = NLog.LogManager.GetLogger("main");
}
protected override void OnLogLevelChanged()
{
foreach(LoggingRule rule in NLog.LogManager.Configuration.LoggingRules)
{
for (int i = 0; i <= (int)LogLevel.Trace; ++i)
{
LogLevel level = (LogLevel)i;
if (IsLevelEnabled(level))
rule.EnableLoggingForLevel(MapLevel(level));
else
rule.DisableLoggingForLevel(MapLevel(level));
}
}
NLog.LogManager.ReconfigExistingLoggers();
}
protected override void DoLogMessage(LogLevel level, string message)
{
_impl.Log(MapLevel(level), message);
}
private NLog.LogLevel MapLevel(LogLevel level)
{
switch(level)
{
case LogLevel.Trace: return NLog.LogLevel.Trace;
case LogLevel.Debug: return NLog.LogLevel.Debug;
case LogLevel.Info: return NLog.LogLevel.Info;
case LogLevel.Warning: return NLog.LogLevel.Warn;
case LogLevel.Error: return NLog.LogLevel.Error;
case LogLevel.Fatal: return NLog.LogLevel.Fatal;
default: return NLog.LogLevel.Trace;
}
}
override public string Path
{
get { return _path; }
}
}
}

View File

@ -0,0 +1,205 @@
/// 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.Runtime.InteropServices;
using System.Text;
namespace Acacia.Native
{
public static class User32
{
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int RegisterClipboardFormat(string format);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SetParent(IntPtr window, IntPtr parent);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr GetParent(IntPtr window);
public static int WS_CHILD = 0x40000000;
public enum GWL : int
{
MSGRESULT = 0,
STYLE = -16
}
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int SetWindowLong(IntPtr hWnd, GWL gwl, int value);
[DllImport("user32.dll")]
public static extern int GetWindowLong(IntPtr hWnd, GWL gwl);
#region Messages
[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int wMsg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
public static extern int PostMessage(IntPtr hWnd, int wMsg, IntPtr wParam, IntPtr lParam);
#endregion
#region DCs
[DllImport("user32.dll")]
public static extern IntPtr GetWindowDC(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
#endregion
#region RedrawWindow
[Flags()]
public enum RedrawWindowFlags : uint
{
Invalidate = 0X1,
InternalPaint = 0X2,
Erase = 0X4,
Validate = 0X8,
NoInternalPaint = 0X10,
NoErase = 0X20,
NoChildren = 0X40,
AllChildren = 0X80,
UpdateNow = 0X100,
EraseNow = 0X200,
Frame = 0X400,
NoFrame = 0X800
}
[DllImport("user32.dll")]
public static extern bool RedrawWindow(IntPtr hWnd, IntPtr lprcUpdate, IntPtr hrgnUpdate, RedrawWindowFlags flags);
#endregion
#region System metrics
public enum SystemMetric : int
{
CXSCREEN = 0,
CYSCREEN = 1,
CXVSCROLL = 2,
CYHSCROLL = 3,
CYCAPTION = 4,
CXBORDER = 5,
CYBORDER = 6,
CXDLGFRAME = 7,
CYDLGFRAME = 8,
CYVTHUMB = 9,
CXHTHUMB = 10,
CXICON = 11,
CYICON = 12,
CXCURSOR = 13,
CYCURSOR = 14,
CYMENU = 15,
CXFULLSCREEN = 16,
CYFULLSCREEN = 17,
CYKANJIWINDOW = 18,
MOUSEPRESENT = 19,
CYVSCROLL = 20,
CXHSCROLL = 21,
DEBUG = 22,
SWAPBUTTON = 23,
RESERVED1 = 24,
RESERVED2 = 25,
RESERVED3 = 26,
RESERVED4 = 27,
CXMIN = 28,
CYMIN = 29,
CXSIZE = 30,
CYSIZE = 31,
CXFRAME = 32,
CYFRAME = 33,
CXMINTRACK = 34,
CYMINTRACK = 35,
CXDOUBLECLK = 36,
CYDOUBLECLK = 37,
CXICONSPACING = 38,
CYICONSPACING = 39,
MENUDROPALIGNMENT = 40,
PENWINDOWS = 41,
DBCSENABLED = 42,
CMOUSEBUTTONS = 43,
CXFIXEDFRAME = CXDLGFRAME, /* ;win40 name change */
CYFIXEDFRAME = CYDLGFRAME, /* ;win40 name change */
CXSIZEFRAME = CXFRAME, /* ;win40 name change */
CYSIZEFRAME = CYFRAME, /* ;win40 name change */
SECURE = 44,
CXEDGE = 45,
CYEDGE = 46,
CXMINSPACING = 47,
CYMINSPACING = 48,
CXSMICON = 49,
CYSMICON = 50,
CYSMCAPTION = 51,
CXSMSIZE = 52,
CYSMSIZE = 53,
CXMENUSIZE = 54,
CYMENUSIZE = 55,
ARRANGE = 56,
CXMINIMIZED = 57,
CYMINIMIZED = 58,
CXMAXTRACK = 59,
CYMAXTRACK = 60,
CXMAXIMIZED = 61,
CYMAXIMIZED = 62,
NETWORK = 63,
CLEANBOOT = 67,
CXDRAG = 68,
CYDRAG = 69,
SHOWSOUNDS = 70,
CXMENUCHECK = 71, /* Use instead of GetMenuCheckMarkDimensions()! */
CYMENUCHECK = 72,
SLOWMACHINE = 73,
MIDEASTENABLED = 74,
MOUSEWHEELPRESENT = 75,
XVIRTUALSCREEN = 76,
YVIRTUALSCREEN = 77,
CXVIRTUALSCREEN = 78,
CYVIRTUALSCREEN = 79,
CMONITORS = 80,
SAMEDISPLAYFORMAT = 81,
IMMENABLED = 82,
CXFOCUSBORDER = 83,
CYFOCUSBORDER = 84,
TABLETPC = 86,
MEDIACENTER = 87,
STARTER = 88,
SERVERR2 = 89,
MOUSEHORIZONTALWHEELPRESENT = 91,
CXPADDEDBORDER = 92,
DIGITIZER = 94,
MAXIMUMTOUCHES = 95,
REMOTESESSION = 0x1000,
SHUTTINGDOWN = 0x2000,
REMOTECONTROL = 0x2001,
CARETBLINKINGENABLED = 0x2002,
CONVERTIBLESLATEMODE = 0x2003,
SYSTEMDOCKED = 0x2004,
}
[DllImport("user32.dll")]
public static extern int GetSystemMetrics(SystemMetric nIndex);
#endregion
}
}

View File

@ -0,0 +1,30 @@
/// Project : Kopano OL Extension
///
/// 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;
namespace Acacia.Native
{
public enum WM : int
{
NCPAINT = 0x0085,
}
}

View File

@ -0,0 +1,249 @@
/// 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;
namespace Acacia
{
public static class OutlookConstants
{
#region Registry
public const string REG_KEY_BASE = @"SOFTWARE\Microsoft\Office\{0}\Outlook\";
public const string REG_SUBKEY_ACCOUNTS = @"Profiles\{0}\9375CFF0413111d3B88A00104B2A6676\";
public const string REG_VAL_ACCOUNTNAME = "Account Name";
public const string REG_VAL_DISPLAYNAME = "Display Name";
public const string REG_VAL_EMAIL = "Email";
public const string REG_VAL_EAS_SERVER = "EAS Server URL";
public const string REG_VAL_EAS_DEVICEID = "EAS DeviceId";
public const string REG_VAL_EAS_USERNAME = "EAS User";
public const string REG_VAL_EAS_PASSWORD = "EAS Password";
public const string REG_VAL_EAS_STOREID = "EAS Store EID";
public const string REG_VAL_IS_ACTIVESYNC = REG_VAL_EAS_USERNAME;
public const string REG_VAL_DELIVERY_STORE = "Delivery Store EntryID";
public const string REG_VAL_DELIVERY_FOLDER = "Delivery Folder EntryID";
public const string REG_VAL_NEXT_ACCOUNT_ID = "NextAccountID";
#endregion
#region PREFIXES
private const string PROP = "http://schemas.microsoft.com/mapi/proptag/0x";
private const string NAMED = "http://schemas.microsoft.com/mapi/string/{00020329-0000-0000-C000-000000000046}/";
private const string GUID = "http://schemas.microsoft.com/mapi/id/";
#endregion
#region Property types
public const string PT_BOOLEAN = "000B";
public const string PT_BINARY = "0102";
public const string PT_MV_BINARY = "1102";
public const string PT_DOUBLE = "0005";
public const string PT_LONG = "0003";
public const string PT_OBJECT = "000D";
public const string PT_STRING8 = "001E";
public const string PT_MV_STRING8 = "101E";
public const string PT_SYSTIME = "0040";
public const string PT_UNICODE = "001F";
public const string PT_MV_UNICODE = "101F";
#endregion
#region General properties
public const string PR_ICON_INDEX = PROP + "1080" + PT_LONG;
public const int PR_ICON_INDEX_NONE = -1;
public const int PR_ICON_INDEX_REPLIED = 261;
public const int PR_ICON_INDEX_FORWARDED = 262;
public const string PR_CATEGORIES = NAMED + "Keywords";
public const string PR_ATTR_HIDDEN = PROP + "10F4" + PT_BOOLEAN;
public const string PR_DISPLAY_NAME = PROP + "3001" + PT_STRING8;
public const string PR_SUBJECT = PROP + "0037" + PT_UNICODE;
#endregion
#region Email specific
public const string PR_LAST_VERB_EXECUTED = PROP + "1081" + PT_LONG;
public const string PR_LAST_VERB_EXECUTION_TIME = PROP + "1082" + PT_SYSTIME;
public const int EXCHIVERB_OPEN = 0;
public const int EXCHIVERB_RESERVED_COMPOSE = 100;
public const int EXCHIVERB_RESERVED_OPEN = 101;
public const int EXCHIVERB_REPLYTOSENDER = 102;
public const int EXCHIVERB_REPLYTOALL = 103;
public const int EXCHIVERB_FORWARD = 104;
public const int EXCHIVERB_PRINT = 105;
public const int EXCHIVERB_SAVEAS = 106;
public const int EXCHIVERB_RESERVED_DELIVERY = 107;
public const int EXCHIVERB_REPLYTOFOLDER = 108;
public const string NS_TRANSPORT_MESSAGE_HEADERS = "http://schemas.microsoft.com/mapi/string/{00020386-0000-0000-C000-000000000046}/";
public const string PR_TRANSPORT_MESSAGE_HEADERS = PROP + "007D" + PT_STRING8;
public const string PR_IN_REPLY_TO_ID = PROP + "1042" + PT_UNICODE;
public const string PR_INTERNET_MESSAGE_ID = PROP + "1035" + PT_UNICODE;
#endregion
#region EAS / ZPush
public const string PR_ZPUSH_MESSAGE_ID = PROP + "6B20" + PT_STRING8;
public const string PR_ZPUSH_FOLDER_ID = PROP + "6A19" + PT_STRING8;
// TODO: names for these, use MFCMAPI
public const string PR_EAS_SYNC1 = PROP + "6A17" + PT_BOOLEAN;
// TODO: this is property zpush_folder_id, that cannot be right?
public const string PR_EAS_SYNCTYPE_ORIG = PROP + "6A19" + PT_UNICODE;
public const string PR_EAS_SYNCTYPE = PROP + "6A1A" + PT_LONG;
public const string PR_EAS_SYNC2 = PROP + "6A1D" + PT_BOOLEAN;
public const string PR_NET_FOLDER_FLAGS = PROP + "36DE" + PT_LONG;
public enum SyncType
{
Other = 1,
Inbox = 2,
Drafts = 3,
WasteBasket = 4,
SentMail = 5,
Outbox = 6,
Task = 7,
Appointment = 8,
Contact = 9,
Note = 10,
Journal = 11,
UserMail = 12,
UserAppointment = 13,
UserContact = 14,
UserTask = 15,
UserJournal = 16,
UserNote = 17,
Unknown = 18,
RecipientCache = 19
}
public static readonly SyncType[] USER_SYNC_TYPES =
{
SyncType.Unknown,
SyncType.Other, // Other = 1,
SyncType.UserMail, // Inbox = 2,
SyncType.UserMail, // Drafts = 3,
SyncType.UserMail, // WasteBasket = 4,
SyncType.UserMail, // SentMail = 5,
SyncType.UserMail, // Outbox = 6,
SyncType.UserTask, // Task = 7,
SyncType.UserAppointment, // Appointment = 8,
SyncType.UserContact, // Contact = 9,
SyncType.UserNote, // Note = 10,
SyncType.UserJournal, // Journal = 11,
SyncType.UserMail,// = 12,
SyncType.UserAppointment,// = 13,
SyncType.UserContact,// = 14,
SyncType.UserTask,// = 15,
SyncType.UserJournal,// = 16,
SyncType.UserNote,// = 17,
SyncType.Unknown, // Unknown = 18,
SyncType.RecipientCache, // RecipientCache = 19
};
public static readonly SyncType[] BASIC_SYNC_TYPES =
{
SyncType.Unknown,
SyncType.Other, // Other = 1,
SyncType.Inbox, // Inbox = 2,
SyncType.Drafts, // Drafts = 3,
SyncType.WasteBasket, // WasteBasket = 4,
SyncType.SentMail, // SentMail = 5,
SyncType.Outbox, // Outbox = 6,
SyncType.Task, // Task = 7,
SyncType.Appointment, // Appointment = 8,
SyncType.Contact, // Contact = 9,
SyncType.Note, // Note = 10,
SyncType.Journal, // Journal = 11,
SyncType.Other,// = 12,
SyncType.Appointment,// = 13,
SyncType.Contact,// = 14,
SyncType.Task,// = 15,
SyncType.Journal,// = 16,
SyncType.Note,// = 17,
SyncType.Unknown, // Unknown = 18,
SyncType.RecipientCache, // RecipientCache = 19
};
public static bool IsMailType(SyncType type)
{
return USER_SYNC_TYPES[(int)type] == SyncType.UserMail;
}
#endregion
#region Contacts & Distribution lists
public const string PREFIX_DISTLIST = GUID + "{00062004-0000-0000-C000-000000000046}/";
public const string PR_DISTLIST_ONEOFFMEMBERS = PREFIX_DISTLIST + "8054" + PT_MV_BINARY;
public const string PR_DISTLIST_MEMBERS = PREFIX_DISTLIST + "8055" + PT_MV_BINARY;
public const string PR_DISPLAY_TYPE = PROP + "3900" + PT_LONG;
public const string PR_DISPLAY_TYPE_EX = PROP + "3905" + PT_LONG;
public const int DT_ROOM = 7;
public const int DT_EQUIPMENT = 8;
public const string PREFIX_CONTACTS = GUID + "{00062004-0000-0000-C000-000000000046}/";
public const string PR_EMAIL1DISPLAYNAME = PREFIX_CONTACTS + "8080" + PT_UNICODE;
public const string PR_EMAIL1ADDRESSTYPE = PREFIX_CONTACTS + "8082" + PT_UNICODE;
public const string PR_EMAIL1EMAILADDRESS = PREFIX_CONTACTS + "8083" + PT_UNICODE;
public const string PR_EMAIL1ORIGINALDISPLAYNAME = PREFIX_CONTACTS + "8084" + PT_UNICODE;
public const string PR_EMAIL1ORIGINALENTRYID = PREFIX_CONTACTS + "8085" + PT_BINARY;
#endregion
#region Notes
public const string PREFIX_NOTES = GUID + "{0006200E-0000-0000-C000-000000000046}/";
public const string PR_NOTE_COLOR = PREFIX_NOTES + "8B00" + PT_LONG;
public const string PR_NOTE_WIDTH = PREFIX_NOTES + "8B02" + PT_LONG;
public const string PR_NOTE_HEIGHT = PREFIX_NOTES + "8B03" + PT_LONG;
public const string PR_NOTE_X = PREFIX_NOTES + "8B04" + PT_LONG;
public const string PR_NOTE_Y = PREFIX_NOTES + "8B05" + PT_LONG;
#endregion
#region Tasks
public const string PREFIX_TASKS = GUID + "{00062008-0000-0000-C000-000000000046}/";
#endregion
#region Message classes
public const string PR_MESSAGE_CLASS = PROP + "001A" + PT_UNICODE;
public const string MESSAGE_CLASS_CONTACTS = "IPM.Contact";
public const string MESSAGE_CLASS_NOTES = "IPM.StickyNote";
#endregion
}
}

View File

@ -0,0 +1,25 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Kopano OL Extension")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Kopano OL Extension")]
[assembly: AssemblyCopyright("Copyright © Kopano b.v. 2016")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("c60a4b51-30f1-433f-99ff-bffc89e5675f")]

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,434 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="OOFGet_Failed" xml:space="preserve">
<value>Unable to retrieve Out of Office settings. You can still enable or disable Out of Office, but applying the settings might fail.</value>
</data>
<data name="OOFGet_Label" xml:space="preserve">
<value>Retrieving current Out of Office settings</value>
</data>
<data name="OOFGet_Title" xml:space="preserve">
<value>Out of Office Assistant</value>
</data>
<data name="OOFSet_DifferentState" xml:space="preserve">
<value>Out of office has been enabled, but the server could not handle your full request.
</value>
</data>
<data name="OOFSet_Disabled" xml:space="preserve">
<value>Out of Office has been disabled.</value>
</data>
<data name="OOFSet_DisableFailed" xml:space="preserve">
<value>Unable to disable Out of Office.</value>
</data>
<data name="OOFSet_Enabled" xml:space="preserve">
<value>Out of Office has been enabled until further notice.</value>
</data>
<data name="OOFSet_EnabledTimeBased" xml:space="preserve">
<value>Out of Office has been enabled from {0} till {1}.</value>
</data>
<data name="OOFSet_EnableFailed" xml:space="preserve">
<value>Unable to enable Out of Office.</value>
</data>
<data name="OOFSet_Failed" xml:space="preserve">
<value>Unable to apply Out of Office settings</value>
</data>
<data name="OOFSet_Label" xml:space="preserve">
<value>Applying Out of Office settings</value>
</data>
<data name="OOFSet_Title" xml:space="preserve">
<value>Out of Office Assistant</value>
</data>
<data name="Ribbon_Debug_Label" xml:space="preserve">
<value>Debug</value>
</data>
<data name="Ribbon_Debug_Screentip" xml:space="preserve">
<value>Debug dialog</value>
</data>
<data name="Ribbon_Debug_Supertip" xml:space="preserve">
<value>Opens the debug dialog, which shows information on the Kopano Outlook Extension.</value>
</data>
<data name="Ribbon_GroupMain_Label" xml:space="preserve">
<value>Kopano</value>
<comment>The group label for the ribbon</comment>
</data>
<data name="Ribbon_OOF_Label" xml:space="preserve">
<value>Out-of-Office</value>
</data>
<data name="Ribbon_OOF_Screentip" xml:space="preserve">
<value>Change Out-of-Office settings</value>
</data>
<data name="Ribbon_OOF_Supertip" xml:space="preserve">
<value>Opens a dialog which allows Out-of-Office settings to be viewed or modified.</value>
</data>
<data name="Ribbon_Settings_Label" xml:space="preserve">
<value>Settings</value>
</data>
<data name="Ribbon_Settings_Screentip" xml:space="preserve">
<value>Settings dialog</value>
</data>
<data name="Ribbon_Settings_Supertip" xml:space="preserve">
<value>Opens the settings dialog, which allows configuration of the plugin and access to support functions.</value>
</data>
<data name="OOFStartup_Message" xml:space="preserve">
<value>Out of Office is currently enabled on account '{0}'. Would you like to change the settings?</value>
</data>
<data name="OOFStartup_Title" xml:space="preserve">
<value>Out of Office Assistant</value>
</data>
<data name="GABEvent_Body" xml:space="preserve">
<value>Modifications to the Global Address Book are not allowed. Please contact your administrator if you think changes are required.</value>
</data>
<data name="GABEvent_Title" xml:space="preserve">
<value>Global Address Book</value>
</data>
<data name="GAB_FolderFormat" xml:space="preserve">
<value>Address Book for {0}</value>
</data>
<data name="LocalStore_DisplayName" xml:space="preserve">
<value>Kopano Folders</value>
</data>
<data name="Feature_DebugSupport" xml:space="preserve">
<value>Support</value>
</data>
<data name="Feature_FreeBusy" xml:space="preserve">
<value>Free/Busy</value>
</data>
<data name="Feature_GAB" xml:space="preserve">
<value>Global Address Book</value>
</data>
<data name="Feature_Notes" xml:space="preserve">
<value>Notes</value>
</data>
<data name="Feature_OutOfOffice" xml:space="preserve">
<value>Out of office</value>
</data>
<data name="Feature_ReplyFlags" xml:space="preserve">
<value>Reply flags</value>
</data>
<data name="ThisAddIn_Title" xml:space="preserve">
<value>Kopano</value>
</data>
<data name="SSLFailed_Body" xml:space="preserve">
<value>There is an error with the security certificate for server {0}. Do you want to allow the connection anyway?</value>
</data>
<data name="SSLFailed_Title" xml:space="preserve">
<value>Certificate error</value>
</data>
<data name="Ribbon_Title" xml:space="preserve">
<value>Kopano</value>
</data>
<data name="SharedFolders_Adding_Label" xml:space="preserve">
<value>Opening shared folder</value>
</data>
<data name="SharedFolders_Adding_Title" xml:space="preserve">
<value>Shared folders</value>
</data>
<data name="SharedFolders_Closing_Confirm" xml:space="preserve">
<value>Close shared folder {0}?</value>
<comment>{0} will be replaced with the folder name</comment>
</data>
<data name="SharedFolders_Closing_Label" xml:space="preserve">
<value>Closing shared folder</value>
</data>
<data name="SharedFolders_Closing_Title" xml:space="preserve">
<value>Shared folders</value>
</data>
<data name="SharedFolders_Adding_Failure" xml:space="preserve">
<value>Unable to open the shared folder. Please ensure you have permission to open the shared folder.</value>
</data>
<data name="SharedFolders_Closing_Failure" xml:space="preserve">
<value>Unable to close the shared folder.</value>
</data>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="Kopano" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Kopano.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Ribbon_WebApp_Label" xml:space="preserve">
<value>Open WebApp</value>
</data>
<data name="Ribbon_WebApp_Screentip" xml:space="preserve">
<value>Open WebApp</value>
</data>
<data name="Ribbon_WebApp_Supertip" xml:space="preserve">
<value>Open WebApp in the system default browser</value>
</data>
<data name="Ribbon_AddSharedFolder" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Icons\Ribbon_AddSharedFolder.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Ribbon_AddSharedFolder_Small" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Icons\Ribbon_AddSharedFolder_Small.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Ribbon_Debug" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Icons\Ribbon_Debug.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Ribbon_Debug_Small" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Icons\Ribbon_Debug_Small.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Ribbon_Logfile" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Icons\Ribbon_Logfile.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Ribbon_Logfile_Small" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Icons\Ribbon_Logfile_Small.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Ribbon_ManageSharedFolders" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Icons\Ribbon_ManageSharedFolders.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Ribbon_ManageSharedFolders_Small" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Icons\Ribbon_ManageSharedFolders_Small.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Ribbon_MDM" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Icons\Ribbon_MDM.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Ribbon_MDM_Small" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Icons\Ribbon_MDM_Small.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Ribbon_OOF" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Icons\Ribbon_OOF.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Ribbon_OOF_Small" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Icons\Ribbon_OOF_Small.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Ribbon_Restore" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Icons\Ribbon_Restore.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Ribbon_Restore_Small" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Icons\Ribbon_Restore_Small.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Ribbon_Rules" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Icons\Ribbon_Rules.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Ribbon_Rules_Small" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Icons\Ribbon_Rules_Small.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Ribbon_Settings" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Icons\Ribbon_Settings.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Ribbon_Settings_Small" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Icons\Ribbon_Settings_Small.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Ribbon_SyncGAB" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Icons\Ribbon_SyncGAB.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Ribbon_SyncGAB_Small" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Icons\Ribbon_SyncGAB_Small.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Ribbon_WebApp" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Icons\Ribbon_WebApp.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Ribbon_WebApp_Small" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Icons\Ribbon_WebApp_Small.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Ribbon_WebMeetings" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Icons\Ribbon_WebMeetings.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Ribbon_WebMeetings_Small" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Icons\Ribbon_WebMeetings_Small.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Ribbon_SharedFolders" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Icons\Ribbon_ManageSharedFolders1.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Ribbon_SharedFolders_Label" xml:space="preserve">
<value>Shared folders</value>
</data>
<data name="Ribbon_SharedFolders_Screentip" xml:space="preserve">
<value>Manage shared folders</value>
</data>
<data name="Ribbon_SharedFolders_Small" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Icons\Ribbon_ManageSharedFolders_Small1.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Ribbon_SharedFolders_Supertip" xml:space="preserve">
<value>Open the "Shared Folders" dialog, which can be used to add or remove shared folders.</value>
</data>
<data name="SharedFolders_PublicFolders" xml:space="preserve">
<value>Public folders</value>
</data>
<data name="SharedFolders_Loading" xml:space="preserve">
<value>Retrieving shared folders</value>
</data>
<data name="SharedFolders_Loading_Error" xml:space="preserve">
<value>There was an error retrieving shared folders</value>
</data>
<data name="SharedFolders_None" xml:space="preserve">
<value>No shared folders are available</value>
</data>
<data name="TreeLoading" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>TreeLoading.gif;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="SharedFolders_Fetching_Failure" xml:space="preserve">
<value>Unable to retrieve shared folders. Please try again later.</value>
</data>
<data name="SharedFolders_Fetching_Label" xml:space="preserve">
<value>Retrieving shared folders</value>
</data>
<data name="SharedFolders_Fetching_Title" xml:space="preserve">
<value>Shared folders</value>
</data>
<data name="SharedFolders_Unsaved_Changes" xml:space="preserve">
<value>There are unsaved changes. Do you really want to to discard these?</value>
</data>
<data name="Ribbon_SharedFolders_Context" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Icons\Ribbon_ManageSharedFolders_Small11.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Ribbon_SharedFolders_Context_Label" xml:space="preserve">
<value>Manage shared folder</value>
</data>
<data name="Ribbon_SharedFolders_Context_Screentip" xml:space="preserve">
<value>Manage this folder in the Shared Folders dialog</value>
</data>
<data name="Ribbon_SharedFolders_Context_Supertip" xml:space="preserve">
<value>Open the "Shared Folders" dialog for the currently selected folder.</value>
</data>
<data name="SharedFolders_Applying_Label" xml:space="preserve">
<value>Applying changes to shared folders</value>
</data>
<data name="SharedFolders_Applying_Failure" xml:space="preserve">
<value>Unable to apply the changes to the shared folders. Please try again later.</value>
</data>
<data name="SharedFolders_Applying_Title" xml:space="preserve">
<value>Shared folders</value>
</data>
<data name="SharedFolders_Permission_None" xml:space="preserve">
<value>None</value>
</data>
<data name="SharedFolders_Permission_Read" xml:space="preserve">
<value>Read</value>
</data>
<data name="SharedFolders_Permission_Write" xml:space="preserve">
<value>Write</value>
</data>
<data name="SharedFolders_Applying_Success" xml:space="preserve">
<value>The changes to the shared folders have been applied successfully.</value>
</data>
<data name="Ribbon_About" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Icons\Ribbon_About.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Ribbon_About_Label" xml:space="preserve">
<value>About</value>
</data>
<data name="Ribbon_About_Screentip" xml:space="preserve">
<value>About dialog</value>
</data>
<data name="Ribbon_About_Small" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Icons\Ribbon_About_Small.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Ribbon_About_Supertip" xml:space="preserve">
<value>Shows the about dialog, which contains licensing and version information.</value>
</data>
</root>

View File

@ -0,0 +1,26 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Acacia.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default {
get {
return defaultInstance;
}
}
}
}

View File

@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings />
</SettingsFile>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 700 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 566 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 773 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 391 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 665 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 587 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 B

Some files were not shown because too many files have changed in this diff Show More